Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Use Knobs to populate AppContext switches #86883

Merged
merged 5 commits into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -214,12 +214,11 @@ The .NET Foundation licenses this file to you under the MIT license.
<IlcArg Condition="'$(CustomNativeMain)' == 'true'" Include="--splitinit" />
<IlcArg Condition="$(ExportsFile) != ''" Include="--exportsfile:$(ExportsFile)" />
<IlcArg Include="@(AutoInitializedAssemblies->'--initassembly:%(Identity)')" />
<IlcArg Include="@(RuntimeHostConfigurationOption->'--appcontextswitch:%(Identity)=%(Value)')" />
<IlcArg Include="--appcontextswitch:RUNTIME_IDENTIFIER=$(RuntimeIdentifier)" />
<IlcArg Include="@(DirectPInvoke->'--directpinvoke:%(Identity)')" />
<IlcArg Include="@(DirectPInvokeList->'--directpinvokelist:%(Identity)')" />
<IlcArg Include="@(_TrimmerFeatureSettings->'--feature:%(Identity)=%(Value)')" />
<IlcArg Include="@(RuntimeHostConfigurationOption->'--runtimeknob:%(Identity)=%(Value)')" />
<IlcArg Include="--runtimeknob:RUNTIME_IDENTIFIER=$(RuntimeIdentifier)" />
<IlcArg Condition="$(ServerGarbageCollection) == 'true'" Include="--runtimeopt:gcServer=1" />
<IlcArg Condition="$(IlcGenerateCompleteTypeMetadata) == 'true' and $(IlcDisableReflection) != 'true'" Include="--completetypemetadata" />
<IlcArg Condition="$(IlcGenerateStackTraceData) == 'true'" Include="--stacktracedata" />
Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/nativeaot/Runtime/MiscHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "GCMemoryHelpers.h"
#include "GCMemoryHelpers.inl"
#include "yieldprocessornormalized.h"
#include "RhConfig.h"

COOP_PINVOKE_HELPER(void, RhDebugBreak, ())
{
Expand Down Expand Up @@ -422,6 +423,13 @@ COOP_PINVOKE_HELPER(int32_t, RhGetProcessCpuCount, ())
return PalGetProcessCpuCount();
}

COOP_PINVOKE_HELPER(uint32_t, RhGetKnobValues, (char *** pResultKeys, char *** pResultValues))
{
*pResultKeys = g_pRhConfig->GetKnobNames();
*pResultValues = g_pRhConfig->GetKnobValues();
return g_pRhConfig->GetKnobCount();
}

#if defined(TARGET_X86) || defined(TARGET_AMD64)
EXTERN_C NATIVEAOT_API void __cdecl RhCpuIdEx(int* cpuInfo, int functionId, int subFunctionId)
{
Expand Down
152 changes: 22 additions & 130 deletions src/coreclr/nativeaot/Runtime/RhConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,8 @@ 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;
extern "C" RhConfig::Config g_compilerEmbeddedSettingsBlob;
extern "C" RhConfig::Config g_compilerEmbeddedKnobsBlob;

bool RhConfig::ReadConfigValue(_In_z_ const char *name, uint64_t* pValue, bool decimal)
{
Expand All @@ -136,7 +130,7 @@ bool RhConfig::ReadConfigValue(_In_z_ const char *name, uint64_t* pValue, bool d

// Check the embedded configuration
const char *embeddedValue = nullptr;
if (GetEmbeddedVariable(&g_embeddedSettings, &g_compilerEmbeddedSettingsBlob, name, true, &embeddedValue))
if (GetEmbeddedVariable(&g_compilerEmbeddedSettingsBlob, name, true, &embeddedValue))
{
*pValue = strtoull(embeddedValue, NULL, decimal ? 10 : 16);
return true;
Expand All @@ -148,7 +142,7 @@ bool RhConfig::ReadConfigValue(_In_z_ const char *name, uint64_t* pValue, bool d
bool RhConfig::ReadKnobUInt64Value(_In_z_ const char *name, uint64_t* pValue)
{
const char *embeddedValue = nullptr;
if (GetEmbeddedVariable(&g_embeddedKnobs, &g_compilerEmbeddedKnobsBlob, name, false, &embeddedValue))
if (GetEmbeddedVariable(&g_compilerEmbeddedKnobsBlob, name, false, &embeddedValue))
{
*pValue = strtoull(embeddedValue, NULL, 10);
return true;
Expand All @@ -160,7 +154,7 @@ bool RhConfig::ReadKnobUInt64Value(_In_z_ const char *name, uint64_t* pValue)
bool RhConfig::ReadKnobBooleanValue(_In_z_ const char *name, bool* pValue)
{
const char *embeddedValue = nullptr;
if (GetEmbeddedVariable(&g_embeddedKnobs, &g_compilerEmbeddedKnobsBlob, name, false, &embeddedValue))
if (GetEmbeddedVariable(&g_compilerEmbeddedKnobsBlob, name, false, &embeddedValue))
{
*pValue = strcmp(embeddedValue, "true") == 0;
return true;
Expand All @@ -169,29 +163,30 @@ bool RhConfig::ReadKnobBooleanValue(_In_z_ const char *name, bool* pValue)
return false;
}

bool RhConfig::GetEmbeddedVariable(void *volatile * embeddedSettings, void* compilerEmbeddedSettingsBlob, _In_z_ const char* configName, bool caseSensitive, _Out_ const char** configValue)
char** RhConfig::GetKnobNames()
{
// Read the config if we haven't yet
if (*embeddedSettings == NULL)
{
ReadEmbeddedSettings(embeddedSettings, compilerEmbeddedSettingsBlob);
}
return g_compilerEmbeddedKnobsBlob.GetKeys();
}

// Config wasn't read or reading failed
if (*embeddedSettings == CONFIG_INI_NOT_AVAIL)
{
return false;
}
char** RhConfig::GetKnobValues()
{
return g_compilerEmbeddedKnobsBlob.GetValues();
}

const ConfigPair* configPairs = (const ConfigPair*)*embeddedSettings;
uint32_t RhConfig::GetKnobCount()
{
return g_compilerEmbeddedKnobsBlob.GetCount();
}

bool RhConfig::GetEmbeddedVariable(Config* config, _In_z_ const char* configName, bool caseSensitive, _Out_ const char** configValue)
{
// Find the first name which matches
for (uint32_t iSettings = 0; iSettings < ((CompilerEmbeddedSettingsBlob*)compilerEmbeddedSettingsBlob)->Size; iSettings++)
for (uint32_t iSettings = 0; iSettings < config->GetCount(); iSettings++)
{
if ((caseSensitive && strcmp(configName, configPairs[iSettings].Key) == 0)
|| (!caseSensitive && _stricmp(configName, configPairs[iSettings].Key) == 0))
if ((caseSensitive && strcmp(configName, config->GetKeyAt(iSettings)) == 0)
|| (!caseSensitive && _stricmp(configName, config->GetKeyAt(iSettings)) == 0))
{
*configValue = configPairs[iSettings].Value;
*configValue = config->GetValueAt(iSettings);
return true;
}
}
Expand All @@ -200,107 +195,4 @@ bool RhConfig::GetEmbeddedVariable(void *volatile * embeddedSettings, void* comp
return false;
}

void RhConfig::ReadEmbeddedSettings(void *volatile * embeddedSettings, void* compilerEmbeddedSettingsBlob)
{
if (*embeddedSettings == NULL)
{
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(embeddedSettings, CONFIG_INI_NOT_AVAIL, NULL);

return;
}

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(embeddedSettings, CONFIG_INI_NOT_AVAIL, NULL);

return;
}

uint32_t iBuff = 0;
uint32_t iIniBuff = 0;
char* currLine;

//while we haven't reached the max number of config pairs, or the end of the file, read the next line
while (iBuff < size)
{
currLine = &data[iBuff];

//find the end of the line
while ((data[iBuff] != '\0') && (iBuff < size))
iBuff++;

//parse the line
//only increment iIniBuff if the parsing succeeded otherwise reuse the config struct
if (ParseConfigLine(&iniBuff[iIniBuff], currLine))
{
iIniBuff++;
}

//advance to the next line;
iBuff++;
}

//if another thread initialized first let the first setter win
//delete the iniBuff to avoid leaking memory
if (PalInterlockedCompareExchangePointer(embeddedSettings, iniBuff, NULL) != NULL)
{
delete[] iniBuff;
}
}

return;
}

//Parses one line of config and populates values in the passed in configPair
//returns: true if the parsing was successful, false if the parsing failed.
//NOTE: if the method fails configPair is left in an uninitialized state
bool RhConfig::ParseConfigLine(_Out_ ConfigPair* configPair, _In_z_ const char * line)
{
uint32_t iLine = 0;
uint32_t iKey = 0;
uint32_t iVal = 0;

//while we haven't reached the end of the key signalled by '=', or the end of the line, or the key maxlen
while (line[iLine] != '=' && line[iLine] != '\0' && iKey < CONFIG_KEY_MAXLEN)
{
configPair->Key[iKey++] = line[iLine++];
}

//if the current char is not '=' we reached the key maxlen, or the line ended return false
if (line[iLine] != '=')
{
return FALSE;
}

configPair->Key[iKey] = '\0';

//increment to start of the value
iLine++;

//while we haven't reached the end of the line, or val maxlen
while (line[iLine] != '\0' && iVal < CONFIG_VAL_MAXLEN)
{
configPair->Value[iVal++] = line[iLine++];
}

//if the current char is not '\0' we didn't reach the end of the line return false
if (line[iLine] != '\0')
{
return FALSE;
}

configPair->Value[iVal] = '\0';

return TRUE;
}

#endif
37 changes: 14 additions & 23 deletions src/coreclr/nativeaot/Runtime/RhConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,20 @@

class RhConfig
{

#define CONFIG_INI_NOT_AVAIL (void*)0x1 //signal for ini file failed to load
#define CONFIG_KEY_MAXLEN 50 //arbitrary max length of config keys increase if needed
#define CONFIG_VAL_MAXLEN 16 //64 bit uint in hex
private:
struct ConfigPair
public:
struct Config
{
uint32_t m_count;
char* m_first[];
public:
char Key[CONFIG_KEY_MAXLEN + 1]; //maxlen + null terminator
char Value[CONFIG_VAL_MAXLEN + 1]; //maxlen + null terminator
uint32_t GetCount() { return m_count; }
char* GetKeyAt(int32_t index) { return m_first[index]; }
char* GetValueAt(int32_t index) { return m_first[m_count + index]; }
char** GetKeys() { return m_first; }
char** GetValues() { return &m_first[m_count]; }
};

// g_embeddedSettings is a buffer of ConfigPair structs embedded in the compiled binary.
//
//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
{
public: // static
Expand All @@ -52,6 +46,10 @@ class RhConfig
bool ReadKnobUInt64Value(_In_z_ const char* wszName, uint64_t* pValue);
bool ReadKnobBooleanValue(_In_z_ const char* wszName, bool* pValue);

char** GetKnobNames();
char** GetKnobValues();
uint32_t GetKnobCount();

#define DEFINE_VALUE_ACCESSOR(_name, defaultVal) \
uint64_t Get##_name() \
{ \
Expand Down Expand Up @@ -99,16 +97,9 @@ class RhConfig
#define CONFIG_FILE_MAXLEN RCV_Count * sizeof(ConfigPair) + 2000

private:
//Parses one line of config and populates values in the passed in configPair
//returns: true if the parsing was successful, false if the parsing failed.
//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 *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(void *volatile * embeddedSettings, void* compilerEmbeddedSettingsBlob, _In_z_ const char* configName, bool caseSensitive, _Out_ const char** configValue);
bool GetEmbeddedVariable(Config* config, _In_z_ const char* configName, bool caseSensitive, _Out_ const char** configValue);

uint32_t m_uiConfigValuesRead;
uint64_t m_uiConfigValues[RCV_Count];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
// 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;
using System.Runtime;
using System.Runtime.ExceptionServices;
using System.Text;

namespace System
{
public static partial class AppContext
{
private static unsafe Dictionary<string, object?> InitializeDataStore()
{
uint count = RuntimeImports.RhGetKnobValues(out byte** keys, out byte** values);

var dataStore = new Dictionary<string, object?>((int)count);
for (int i = 0; i < count; i++)
{
dataStore.Add(
Encoding.UTF8.GetString(keys[i], string.strlen(keys[i])),
Encoding.UTF8.GetString(values[i], string.strlen(values[i])));
}

return dataStore;
}

[RuntimeExport("OnFirstChanceException")]
internal static void OnFirstChanceException(object e)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,10 @@ internal static IntPtr RhGetModuleSection(TypeManagerHandle module, ReadyToRunSe
[RuntimeImport(RuntimeLibrary, "RhGetLoadedOSModules")]
internal static extern uint RhGetLoadedOSModules(IntPtr[] resultArray);

[MethodImplAttribute(MethodImplOptions.InternalCall)]
[RuntimeImport(RuntimeLibrary, "RhGetKnobValues")]
internal static extern unsafe uint RhGetKnobValues(out byte** keyArray, out byte** valueArray);

[MethodImplAttribute(MethodImplOptions.InternalCall)]
[RuntimeImport(RuntimeLibrary, "RhGetOSModuleFromPointer")]
internal static extern IntPtr RhGetOSModuleFromPointer(IntPtr pointerVal);
Expand Down
Loading