diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
index 607028701561f4..4e5a512c1d785a 100644
--- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
+++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
@@ -218,6 +218,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 928778b09f4650..0b965c18994f86 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 214a79c5c93b24..a3cd83493cab96 100644
--- a/src/coreclr/nativeaot/Runtime/RhConfig.h
+++ b/src/coreclr/nativeaot/Runtime/RhConfig.h
@@ -35,6 +35,7 @@ class RhConfig
//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 @@ class RhConfig
};
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 @@ class RhConfig
//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 5f60b30ecfe42c..68e77f65c5d1ba 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 00000000000000..db1d0236a1556e
--- /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 255763aaacac0f..724df1c41ccfcc 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 a417d22aa7c87c..4c52191cd67f09 100644
--- a/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs
+++ b/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs
@@ -92,6 +92,8 @@ internal sealed class ILCompilerRootCommand : RootCommand
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 =>
{
@@ -205,6 +207,7 @@ public ILCompilerRootCommand(string[] args) : base(".NET Native IL Compiler")
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 7fc33c7a1c77f4..9abc4543d3a105 100644
--- a/src/coreclr/tools/aot/ILCompiler/Program.cs
+++ b/src/coreclr/tools/aot/ILCompiler/Program.cs
@@ -209,12 +209,14 @@ public int Run()
}
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 @@ public int Run()
{
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)
{