Read public GC options if available (#86068)
authorAndrii Kurdiumov <kant2002@gmail.com>
Tue, 30 May 2023 03:44:37 +0000 (09:44 +0600)
committerGitHub <noreply@github.com>
Tue, 30 May 2023 03:44:37 +0000 (12:44 +0900)
src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
src/coreclr/nativeaot/Runtime/RhConfig.cpp
src/coreclr/nativeaot/Runtime/RhConfig.h
src/coreclr/nativeaot/Runtime/gcrhenv.cpp
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/RuntimeKnobsRootProvider.cs [new file with mode: 0644]
src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs
src/coreclr/tools/aot/ILCompiler/Program.cs

index 5736aa8..9ce8ef0 100644 (file)
@@ -219,6 +219,7 @@ The .NET Foundation licenses this file to you under the MIT license.
       <IlcArg Include="@(DirectPInvoke->'--directpinvoke:%(Identity)')" />
       <IlcArg Include="@(DirectPInvokeList->'--directpinvokelist:%(Identity)')" />
       <IlcArg Include="@(_TrimmerFeatureSettings->'--feature:%(Identity)=%(Value)')" />
+      <IlcArg Include="@(RuntimeHostConfigurationOption->'--runtimeknob:%(Identity)=%(Value)')" />
       <IlcArg Condition="$(ServerGarbageCollection) == 'true'" Include="--runtimeopt:gcServer=1" />
       <IlcArg Condition="$(IlcGenerateCompleteTypeMetadata) == 'true' and $(IlcDisableReflection) != 'true'" Include="--completetypemetadata" />
       <IlcArg Condition="$(IlcGenerateStackTraceData) == 'true'" Include="--stacktracedata" />
index 928778b..0b965c1 100644 (file)
@@ -120,6 +120,15 @@ bool RhConfig::Environment::TryGetStringValue(const char* name, char** value)
     return true;
 }
 
+struct CompilerEmbeddedSettingsBlob
+{
+    uint32_t Size;
+    char Data[1];
+};
+
+extern "C" CompilerEmbeddedSettingsBlob g_compilerEmbeddedSettingsBlob;
+extern "C" CompilerEmbeddedSettingsBlob g_compilerEmbeddedKnobsBlob;
+
 bool RhConfig::ReadConfigValue(_In_z_ const char *name, uint64_t* pValue, bool decimal)
 {
     if (Environment::TryGetIntegerValue(name, pValue, decimal))
@@ -127,7 +136,7 @@ bool RhConfig::ReadConfigValue(_In_z_ const char *name, uint64_t* pValue, bool d
 
     // Check the embedded configuration
     const char *embeddedValue = nullptr;
-    if (GetEmbeddedVariable(name, &embeddedValue))
+    if (GetEmbeddedVariable(&g_embeddedSettings, &g_compilerEmbeddedSettingsBlob, name, true, &embeddedValue))
     {
         *pValue = strtoull(embeddedValue, NULL, decimal ? 10 : 16);
         return true;
@@ -136,26 +145,51 @@ bool RhConfig::ReadConfigValue(_In_z_ const char *name, uint64_t* pValue, bool d
     return false;
 }
 
-bool RhConfig::GetEmbeddedVariable(_In_z_ const char* configName, _Out_ const char** configValue)
+bool RhConfig::ReadKnobUInt64Value(_In_z_ const char *name, uint64_t* pValue)
+{
+    const char *embeddedValue = nullptr;
+    if (GetEmbeddedVariable(&g_embeddedKnobs, &g_compilerEmbeddedKnobsBlob, name, false, &embeddedValue))
+    {
+        *pValue = strtoull(embeddedValue, NULL, 10);
+        return true;
+    }
+
+    return false;
+}
+
+bool RhConfig::ReadKnobBooleanValue(_In_z_ const char *name, bool* pValue)
+{
+    const char *embeddedValue = nullptr;
+    if (GetEmbeddedVariable(&g_embeddedKnobs, &g_compilerEmbeddedKnobsBlob, name, false, &embeddedValue))
+    {
+        *pValue = strcmp(embeddedValue, "true") == 0;
+        return true;
+    }
+
+    return false;
+}
+
+bool RhConfig::GetEmbeddedVariable(void *volatile * embeddedSettings, void* compilerEmbeddedSettingsBlob, _In_z_ const char* configName, bool caseSensitive, _Out_ const char** configValue)
 {
     // Read the config if we haven't yet
-    if (g_embeddedSettings == NULL)
+    if (*embeddedSettings == NULL)
     {
-        ReadEmbeddedSettings();
+        ReadEmbeddedSettings(embeddedSettings, compilerEmbeddedSettingsBlob);
     }
 
     // Config wasn't read or reading failed
-    if (g_embeddedSettings == CONFIG_INI_NOT_AVAIL)
+    if (*embeddedSettings == CONFIG_INI_NOT_AVAIL)
     {
         return false;
     }
 
-    const ConfigPair* configPairs = (const ConfigPair*)g_embeddedSettings;
+    const ConfigPair* configPairs = (const ConfigPair*)*embeddedSettings;
 
-    // Find the first name which matches (case insensitive to be compat with environment variable counterpart)
-    for (int iSettings = 0; iSettings < RCV_Count; iSettings++)
+    // Find the first name which matches
+    for (uint32_t iSettings = 0; iSettings < ((CompilerEmbeddedSettingsBlob*)compilerEmbeddedSettingsBlob)->Size; iSettings++)
     {
-        if (_stricmp(configName, configPairs[iSettings].Key) == 0)
+        if ((caseSensitive && strcmp(configName, configPairs[iSettings].Key) == 0)
+            || (!caseSensitive && _stricmp(configName, configPairs[iSettings].Key) == 0))
         {
             *configValue = configPairs[iSettings].Value;
             return true;
@@ -166,32 +200,27 @@ bool RhConfig::GetEmbeddedVariable(_In_z_ const char* configName, _Out_ const ch
     return false;
 }
 
-struct CompilerEmbeddedSettingsBlob
+void RhConfig::ReadEmbeddedSettings(void *volatile * embeddedSettings, void* compilerEmbeddedSettingsBlob)
 {
-    uint32_t Size;
-    char Data[1];
-};
-
-extern "C" CompilerEmbeddedSettingsBlob g_compilerEmbeddedSettingsBlob;
-
-void RhConfig::ReadEmbeddedSettings()
-{
-    if (g_embeddedSettings == NULL)
+    if (*embeddedSettings == NULL)
     {
-        //if reading the file contents failed set g_embeddedSettings to CONFIG_INI_NOT_AVAIL
-        if (g_compilerEmbeddedSettingsBlob.Size == 0)
+        uint32_t size = ((CompilerEmbeddedSettingsBlob*)compilerEmbeddedSettingsBlob)->Size;
+        char* data = ((CompilerEmbeddedSettingsBlob*)compilerEmbeddedSettingsBlob)->Data;
+
+        //if reading the file contents failed set embeddedSettings to CONFIG_INI_NOT_AVAIL
+        if (size == 0)
         {
             //only set if another thread hasn't initialized the buffer yet, otherwise ignore and let the first setter win
-            PalInterlockedCompareExchangePointer(&g_embeddedSettings, CONFIG_INI_NOT_AVAIL, NULL);
+            PalInterlockedCompareExchangePointer(embeddedSettings, CONFIG_INI_NOT_AVAIL, NULL);
 
             return;
         }
 
-        ConfigPair* iniBuff = new (nothrow) ConfigPair[RCV_Count];
+        ConfigPair* iniBuff = new (nothrow) ConfigPair[size];
         if (iniBuff == NULL)
         {
             //only set if another thread hasn't initialized the buffer yet, otherwise ignore and let the first setter win
-            PalInterlockedCompareExchangePointer(&g_embeddedSettings, CONFIG_INI_NOT_AVAIL, NULL);
+            PalInterlockedCompareExchangePointer(embeddedSettings, CONFIG_INI_NOT_AVAIL, NULL);
 
             return;
         }
@@ -201,12 +230,12 @@ void RhConfig::ReadEmbeddedSettings()
         char* currLine;
 
         //while we haven't reached the max number of config pairs, or the end of the file, read the next line
-        while (iIniBuff < RCV_Count && iBuff < g_compilerEmbeddedSettingsBlob.Size)
+        while (iBuff < size)
         {
-            currLine = &g_compilerEmbeddedSettingsBlob.Data[iBuff];
+            currLine = &data[iBuff];
 
             //find the end of the line
-            while ((g_compilerEmbeddedSettingsBlob.Data[iBuff] != '\0') && (iBuff < g_compilerEmbeddedSettingsBlob.Size))
+            while ((data[iBuff] != '\0') && (iBuff < size))
                 iBuff++;
 
             //parse the line
@@ -220,17 +249,9 @@ void RhConfig::ReadEmbeddedSettings()
             iBuff++;
         }
 
-        //initialize the remaining config pairs to "\0"
-        while (iIniBuff < RCV_Count)
-        {
-            iniBuff[iIniBuff].Key[0] = '\0';
-            iniBuff[iIniBuff].Value[0] = '\0';
-            iIniBuff++;
-        }
-
         //if another thread initialized first let the first setter win
         //delete the iniBuff to avoid leaking memory
-        if (PalInterlockedCompareExchangePointer(&g_embeddedSettings, iniBuff, NULL) != NULL)
+        if (PalInterlockedCompareExchangePointer(embeddedSettings, iniBuff, NULL) != NULL)
         {
             delete[] iniBuff;
         }
index 214a79c..a3cd834 100644 (file)
@@ -35,6 +35,7 @@ private:
     //NOTE: g_embeddedSettings is only set in ReadEmbeddedSettings and must be set atomically only once
     //      using PalInterlockedCompareExchangePointer to avoid races when initializing
     void* volatile g_embeddedSettings = NULL;
+    void* volatile g_embeddedKnobs = NULL;
 
 public:
     class Environment
@@ -48,6 +49,8 @@ public:
     };
 
     bool ReadConfigValue(_In_z_ const char* wszName, uint64_t* pValue, bool decimal = false);
+    bool ReadKnobUInt64Value(_In_z_ const char* wszName, uint64_t* pValue);
+    bool ReadKnobBooleanValue(_In_z_ const char* wszName, bool* pValue);
 
 #define DEFINE_VALUE_ACCESSOR(_name, defaultVal)        \
     uint64_t Get##_name()                                 \
@@ -101,11 +104,11 @@ private:
     //NOTE: if the method fails configPair is left in an uninitialized state
     bool ParseConfigLine(_Out_ ConfigPair* configPair, _In_z_ const char * line);
 
-    void ReadEmbeddedSettings();
+    void ReadEmbeddedSettings(void *volatile * embeddedSettings, void* compilerEmbeddedSettingsBlob);
 
     // Gets a pointer to the embedded configuration value. Memory is held by the callee.
     // Returns true if the variable was found, false otherwise
-    bool GetEmbeddedVariable(_In_z_ const char* configName, _Out_ const char** configValue);
+    bool GetEmbeddedVariable(void *volatile * embeddedSettings, void* compilerEmbeddedSettingsBlob, _In_z_ const char* configName, bool caseSensitive, _Out_ const char** configValue);
 
     uint32_t  m_uiConfigValuesRead;
     uint64_t  m_uiConfigValues[RCV_Count];
index 5f60b30..68e77f6 100644 (file)
@@ -1367,11 +1367,21 @@ bool GCToEEInterface::GetBooleanConfigValue(const char* privateKey, const char*
     }
 
     uint64_t uiValue;
-    if (!g_pRhConfig->ReadConfigValue(privateKey, &uiValue))
-        return false;
+    if (g_pRhConfig->ReadConfigValue(privateKey, &uiValue))
+    {
+        *value = uiValue != 0;
+        return true;
+    }
 
-    *value = uiValue != 0;
-    return true;
+    if (publicKey)
+    {
+        if (g_pRhConfig->ReadKnobBooleanValue(publicKey, value))
+        {
+            return true;
+        }
+    }
+
+    return false;
 }
 
 extern GCHeapHardLimitInfo g_gcHeapHardLimitInfo;
@@ -1379,14 +1389,6 @@ extern bool g_gcHeapHardLimitInfoSpecified;
 
 bool GCToEEInterface::GetIntConfigValue(const char* privateKey, const char* publicKey, int64_t* value)
 {
-#ifdef UNICODE
-    size_t keyLength = strlen(privateKey) + 1;
-    TCHAR* pKey = (TCHAR*)_alloca(sizeof(TCHAR) * keyLength);
-    for (size_t i = 0; i < keyLength; i++)
-        pKey[i] = privateKey[i];
-#else
-    const TCHAR* pKey = privateKey;
-#endif
     if (g_gcHeapHardLimitInfoSpecified)
     {
         if ((g_gcHeapHardLimitInfo.heapHardLimit != UINT64_MAX) && strcmp(privateKey, "GCHeapHardLimit") == 0) { *value = g_gcHeapHardLimitInfo.heapHardLimit; return true; }
@@ -1400,11 +1402,22 @@ bool GCToEEInterface::GetIntConfigValue(const char* privateKey, const char* publ
     }
 
     uint64_t uiValue;
-    if (!g_pRhConfig->ReadConfigValue(privateKey, &uiValue))
-        return false;
+    if (g_pRhConfig->ReadConfigValue(privateKey, &uiValue))
+    {
+        *value = uiValue;
+        return true;
+    }
 
-    *value = uiValue;
-    return true;
+    if (publicKey)
+    {
+        if (g_pRhConfig->ReadKnobUInt64Value(publicKey, &uiValue))
+        {
+            *value = uiValue;
+            return true;
+        }
+    }
+
+    return false;
 }
 
 void GCToEEInterface::LogErrorToHost(const char *message)
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/RuntimeKnobsRootProvider.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/RuntimeKnobsRootProvider.cs
new file mode 100644 (file)
index 0000000..db1d023
--- /dev/null
@@ -0,0 +1,57 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+
+namespace ILCompiler
+{
+    /// <summary>
+    /// A root provider that provides a runtime configuration blob that influences runtime behaviors.
+    /// See RhConfigValues.h for allowed values.
+    /// </summary>
+    public class RuntimeKnobsRootProvider : ICompilationRootProvider
+    {
+        private readonly IEnumerable<string> _runtimeKnobs;
+
+        public RuntimeKnobsRootProvider(IEnumerable<string> runtimeKnobs)
+        {
+            _runtimeKnobs = runtimeKnobs;
+        }
+
+        void ICompilationRootProvider.AddCompilationRoots(IRootingServiceProvider rootProvider)
+        {
+            rootProvider.RootReadOnlyDataBlob(GetRuntimeKnobsBlob(), 4, "Runtime configuration knobs", "g_compilerEmbeddedKnobsBlob");
+        }
+
+        protected byte[] GetRuntimeKnobsBlob()
+        {
+            const int HeaderSize = 4;
+
+            ArrayBuilder<byte> options = default(ArrayBuilder<byte>);
+
+            // Reserve space for the header
+            options.ZeroExtend(HeaderSize);
+
+            foreach (string option in _runtimeKnobs)
+            {
+                byte[] optionBytes = System.Text.Encoding.UTF8.GetBytes(option);
+                options.Append(optionBytes);
+
+                // Emit a null to separate the next option
+                options.Add(0);
+            }
+
+            byte[] result = options.ToArray();
+
+            int length = options.Count - HeaderSize;
+
+            // Encode the size of the blob into the header
+            result[0] = (byte)length;
+            result[1] = (byte)(length >> 8);
+            result[2] = (byte)(length >> 0x10);
+            result[3] = (byte)(length >> 0x18);
+
+            return result;
+        }
+    }
+}
index 255763a..724df1c 100644 (file)
     <Compile Include="Compiler\DependencyAnalysis\Target_LoongArch64\LoongArch64ReadyToRunHelperNode.cs" />
     <Compile Include="Compiler\DependencyAnalysis\Target_LoongArch64\LoongArch64ReadyToRunGenericHelperNode.cs" />
     <Compile Include="Compiler\DependencyAnalysis\Target_LoongArch64\LoongArch64UnboxingStubNode.cs" />
+    <Compile Include="Compiler\RuntimeKnobsRootProvider.cs" />
     <Compile Include="Compiler\UnmanagedEntryPointsRootProvider.cs" />
     <Compile Include="Compiler\GenericDictionaryLookup.cs" />
     <Compile Include="Compiler\IRootingServiceProvider.cs" />
index f6f81e8..ff80a24 100644 (file)
@@ -94,6 +94,8 @@ namespace ILCompiler
             new(new[] { "--feature" }, Array.Empty<string>, "Feature switches to apply (format: 'Namespace.Name=[true|false]'");
         public Option<string[]> RuntimeOptions { get; } =
             new(new[] { "--runtimeopt" }, Array.Empty<string>, "Runtime options to set");
+        public Option<string[]> RuntimeKnobs { get; } =
+            new(new[] { "--runtimeknob" }, Array.Empty<string>, "Runtime knobs to set");
         public Option<int> Parallelism { get; } =
             new(new[] { "--parallelism" }, result =>
             {
@@ -208,6 +210,7 @@ namespace ILCompiler
             AddOption(AppContextSwitches);
             AddOption(FeatureSwitches);
             AddOption(RuntimeOptions);
+            AddOption(RuntimeKnobs);
             AddOption(Parallelism);
             AddOption(InstructionSet);
             AddOption(Guard);
index 485539c..3649352 100644 (file)
@@ -209,12 +209,14 @@ namespace ILCompiler
                 }
 
                 string[] runtimeOptions = Get(_command.RuntimeOptions);
+                string[] runtimeKnobs = Get(_command.RuntimeKnobs);
                 if (nativeLib)
                 {
                     // Set owning module of generated native library startup method to compiler generated module,
                     // to ensure the startup method is included in the object file during multimodule mode build
                     compilationRoots.Add(new NativeLibraryInitializerRootProvider(typeSystemContext.GeneratedAssembly, CreateInitializerList(typeSystemContext)));
                     compilationRoots.Add(new RuntimeConfigurationRootProvider(runtimeOptions));
+                    compilationRoots.Add(new RuntimeKnobsRootProvider(runtimeKnobs));
                     compilationRoots.Add(new ExpectedIsaFeaturesRootProvider(instructionSetSupport));
                     if (SplitExeInitialization)
                     {
@@ -225,6 +227,7 @@ namespace ILCompiler
                 {
                     compilationRoots.Add(new MainMethodRootProvider(entrypointModule, CreateInitializerList(typeSystemContext), generateLibraryAndModuleInitializers: !SplitExeInitialization));
                     compilationRoots.Add(new RuntimeConfigurationRootProvider(runtimeOptions));
+                    compilationRoots.Add(new RuntimeKnobsRootProvider(runtimeKnobs));
                     compilationRoots.Add(new ExpectedIsaFeaturesRootProvider(instructionSetSupport));
                     if (SplitExeInitialization)
                     {