From 15f5f79950841a42d136692b7a25d1b42feb810a Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Tue, 30 May 2023 09:44:37 +0600 Subject: [PATCH] Read public GC options if available (#86068) --- .../Microsoft.NETCore.Native.targets | 1 + src/coreclr/nativeaot/Runtime/RhConfig.cpp | 93 +++++++++++++--------- src/coreclr/nativeaot/Runtime/RhConfig.h | 7 +- src/coreclr/nativeaot/Runtime/gcrhenv.cpp | 45 +++++++---- .../Compiler/RuntimeKnobsRootProvider.cs | 57 +++++++++++++ .../ILCompiler.Compiler/ILCompiler.Compiler.csproj | 1 + .../tools/aot/ILCompiler/ILCompilerRootCommand.cs | 3 + src/coreclr/tools/aot/ILCompiler/Program.cs | 3 + 8 files changed, 156 insertions(+), 54 deletions(-) create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/RuntimeKnobsRootProvider.cs diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets index 5736aa8..9ce8ef0 100644 --- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets @@ -219,6 +219,7 @@ The .NET Foundation licenses this file to you under the MIT license. + diff --git a/src/coreclr/nativeaot/Runtime/RhConfig.cpp b/src/coreclr/nativeaot/Runtime/RhConfig.cpp index 928778b..0b965c1 100644 --- a/src/coreclr/nativeaot/Runtime/RhConfig.cpp +++ b/src/coreclr/nativeaot/Runtime/RhConfig.cpp @@ -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; } diff --git a/src/coreclr/nativeaot/Runtime/RhConfig.h b/src/coreclr/nativeaot/Runtime/RhConfig.h index 214a79c..a3cd834 100644 --- a/src/coreclr/nativeaot/Runtime/RhConfig.h +++ b/src/coreclr/nativeaot/Runtime/RhConfig.h @@ -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]; diff --git a/src/coreclr/nativeaot/Runtime/gcrhenv.cpp b/src/coreclr/nativeaot/Runtime/gcrhenv.cpp index 5f60b30..68e77f6 100644 --- a/src/coreclr/nativeaot/Runtime/gcrhenv.cpp +++ b/src/coreclr/nativeaot/Runtime/gcrhenv.cpp @@ -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 index 0000000..db1d023 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/RuntimeKnobsRootProvider.cs @@ -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 +{ + /// + /// A root provider that provides a runtime configuration blob that influences runtime behaviors. + /// See RhConfigValues.h for allowed values. + /// + public class RuntimeKnobsRootProvider : ICompilationRootProvider + { + private readonly IEnumerable _runtimeKnobs; + + public RuntimeKnobsRootProvider(IEnumerable 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 options = default(ArrayBuilder); + + // 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; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index 255763a..724df1c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -579,6 +579,7 @@ + diff --git a/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs b/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs index f6f81e8..ff80a24 100644 --- a/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs +++ b/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs @@ -94,6 +94,8 @@ namespace ILCompiler new(new[] { "--feature" }, Array.Empty, "Feature switches to apply (format: 'Namespace.Name=[true|false]'"); public Option RuntimeOptions { get; } = new(new[] { "--runtimeopt" }, Array.Empty, "Runtime options to set"); + public Option RuntimeKnobs { get; } = + new(new[] { "--runtimeknob" }, Array.Empty, "Runtime knobs to set"); public Option 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); diff --git a/src/coreclr/tools/aot/ILCompiler/Program.cs b/src/coreclr/tools/aot/ILCompiler/Program.cs index 485539c..3649352 100644 --- a/src/coreclr/tools/aot/ILCompiler/Program.cs +++ b/src/coreclr/tools/aot/ILCompiler/Program.cs @@ -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) { -- 2.7.4