summaryrefslogtreecommitdiff
path: root/src/ToolBox/superpmi/superpmi/parallelsuperpmi.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ToolBox/superpmi/superpmi/parallelsuperpmi.cpp')
-rw-r--r--src/ToolBox/superpmi/superpmi/parallelsuperpmi.cpp587
1 files changed, 587 insertions, 0 deletions
diff --git a/src/ToolBox/superpmi/superpmi/parallelsuperpmi.cpp b/src/ToolBox/superpmi/superpmi/parallelsuperpmi.cpp
new file mode 100644
index 0000000000..8c5232315e
--- /dev/null
+++ b/src/ToolBox/superpmi/superpmi/parallelsuperpmi.cpp
@@ -0,0 +1,587 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+#include "standardpch.h"
+#include "superpmi.h"
+#include "simpletimer.h"
+#include "mclist.h"
+#include "commandline.h"
+#include "errorhandling.h"
+
+#define MAX_LOG_LINE_SIZE 0x1000 //4 KB
+
+bool closeRequested = false; // global variable to communicate CTRL+C between threads.
+
+bool StartProcess(char *commandLine, HANDLE hStdOutput, HANDLE hStdError, HANDLE *hProcess)
+{
+ LogDebug("StartProcess commandLine=%s", commandLine);
+
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+
+ ZeroMemory(&si, sizeof(si));
+ si.cb = sizeof(si);
+ si.dwFlags = STARTF_USESTDHANDLES;
+ si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
+ si.hStdOutput = hStdOutput;
+ si.hStdError = hStdError;
+
+ ZeroMemory(&pi, sizeof(pi));
+
+ // Start the child process.
+ if (!CreateProcess(NULL, // No module name (use command line)
+ commandLine, // Command line
+ NULL, // Process handle not inheritable
+ NULL, // Thread handle not inheritable
+ TRUE, // Set handle inheritance to TRUE (required to use STARTF_USESTDHANDLES)
+ 0, // No creation flags
+ NULL, // Use parent's environment block
+ NULL, // Use parent's starting directory
+ &si, // Pointer to STARTUPINFO structure
+ &pi)) // Pointer to PROCESS_INFORMATION structure
+ {
+ LogError("CreateProcess failed (%d). CommandLine: %s", GetLastError(), commandLine);
+ *hProcess = INVALID_HANDLE_VALUE;
+ return false;
+ }
+
+ *hProcess = pi.hProcess;
+ return true;
+}
+
+void ReadMCLToArray(char *mclFilename, int **arr, int *count)
+{
+ *count = 0;
+ *arr = nullptr;
+ char* buff = nullptr;
+
+ HANDLE hFile = CreateFileA(mclFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ {
+ LogError("Unable to open '%s'. GetLastError()=%u", mclFilename, GetLastError());
+ goto Cleanup;
+ }
+
+ LARGE_INTEGER DataTemp;
+ if (GetFileSizeEx(hFile, &DataTemp) == 0)
+ {
+ LogError("GetFileSizeEx failed. GetLastError()=%u", GetLastError());
+ goto Cleanup;
+ }
+
+ if (DataTemp.QuadPart > MAXMCLFILESIZE)
+ {
+ LogError("Size %d exceeds max size of %d", DataTemp.QuadPart, MAXMCLFILESIZE);
+ goto Cleanup;
+ }
+
+ int sz;
+ sz = (int)(DataTemp.QuadPart);
+
+ buff = new char[sz];
+ DWORD bytesRead;
+ if (ReadFile(hFile, buff, sz, &bytesRead, nullptr) == 0)
+ {
+ LogError("ReadFile failed. GetLastError()=%u", GetLastError());
+ goto Cleanup;
+ }
+
+ for (int i = 0; i < sz; i++)
+ {
+ if (buff[i] == 0x0d)
+ (*count)++;
+ }
+
+ if (*count <= 0)
+ return;
+
+ *arr = new int[*count];
+ for (int j = 0, arrIndex = 0; j < sz; )
+ {
+ //seek the first number on the line
+ while (!isdigit((unsigned char)buff[j]))
+ j++;
+ //read in the number
+ (*arr)[arrIndex++] = atoi(&buff[j]);
+ //seek to the start of next line
+ while ((j < sz) && (buff[j] != 0x0a))
+ j++;
+ j++;
+ }
+
+Cleanup:
+ if (buff != nullptr)
+ delete[] buff;
+
+ if (hFile != INVALID_HANDLE_VALUE)
+ CloseHandle(hFile);
+}
+
+bool WriteArrayToMCL(char *mclFilename, int *arr, int count)
+{
+ HANDLE hMCLFile = CreateFileA(mclFilename, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ bool result = true;
+
+ if (hMCLFile == INVALID_HANDLE_VALUE)
+ {
+ LogError("Failed to open output file '%s'. GetLastError()=%u", mclFilename, GetLastError());
+ result = false;
+ goto Cleanup;
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ char strMethodIndex[12];
+ DWORD charCount = 0;
+ DWORD bytesWritten = 0;
+
+ charCount = sprintf(strMethodIndex, "%d\r\n", arr[i]);
+
+ if (!WriteFile(hMCLFile, strMethodIndex, charCount, &bytesWritten, nullptr) || (bytesWritten != charCount))
+ {
+ LogError("Failed to write method index '%d'. GetLastError()=%u", strMethodIndex, GetLastError());
+ result = false;
+ goto Cleanup;
+ }
+ }
+
+Cleanup:
+ if (hMCLFile != INVALID_HANDLE_VALUE)
+ CloseHandle(hMCLFile);
+
+ return result;
+}
+
+void ProcessChildStdErr(char *stderrFilename)
+{
+ char buff[MAX_LOG_LINE_SIZE];
+
+ FILE *fp = fopen(stderrFilename, "r");
+
+ if (fp == NULL)
+ {
+ LogError("Unable to open '%s'.", stderrFilename);
+ goto Cleanup;
+ }
+
+ while (fgets(buff, MAX_LOG_LINE_SIZE, fp) != NULL)
+ {
+ //get rid of the '\n' at the end of line
+ size_t buffLen = strlen(buff);
+ if (buff[buffLen - 1] == '\n')
+ buff[buffLen - 1] = 0;
+
+ if (strncmp(buff, "ERROR: ", 7) == 0)
+ LogError("%s", &buff[7]); //log as Error and remove the "ERROR: " in front
+ else if (strncmp(buff, "WARNING: ", 9) == 0)
+ LogWarning("%s", &buff[9]); //log as Warning and remove the "WARNING: " in front
+ else if (strlen(buff) > 0)
+ LogWarning("%s", buff); //unknown output, log it as a warning
+ }
+
+Cleanup:
+ if (fp != NULL)
+ fclose(fp);
+}
+
+void ProcessChildStdOut(const CommandLine::Options& o, char *stdoutFilename, int *loaded, int *jitted, int *failed, int *diffs, bool *usageError)
+{
+ char buff[MAX_LOG_LINE_SIZE];
+
+ FILE *fp = fopen(stdoutFilename, "r");
+
+ if (fp == NULL)
+ {
+ LogError("Unable to open '%s'.", stdoutFilename);
+ goto Cleanup;
+ }
+
+ while (fgets(buff, MAX_LOG_LINE_SIZE, fp) != NULL)
+ {
+ //get rid of the '\n' at the end of line
+ size_t buffLen = strlen(buff);
+ if (buff[buffLen - 1] == '\n')
+ buff[buffLen - 1] = 0;
+
+ if (strncmp(buff, "MISSING: ", 9) == 0)
+ LogMissing("%s", &buff[9]); //log as Missing and remove the "MISSING: " in front
+ else if (strncmp(buff, "ISSUE: ", 7) == 0)
+ {
+ if (strncmp(&buff[7], "<ASM_DIFF> ", 11) == 0)
+ LogIssue(ISSUE_ASM_DIFF, "%s", &buff[18]); //log as Issue and remove the "ISSUE: <ASM_DIFF>" in front
+ else if (strncmp(&buff[7], "<ASSERT> ", 9) == 0)
+ LogIssue(ISSUE_ASSERT, "%s", &buff[16]); //log as Issue and remove the "ISSUE: <ASSERT>" in front
+ }
+ else if (strncmp(buff, g_SuperPMIUsageFirstLine, strlen(g_SuperPMIUsageFirstLine)) == 0)
+ {
+ *usageError = true; //Signals that we had a SuperPMI command line usage error
+
+ //Read the entire stdout file and printf it
+ printf("%s", buff);
+ while (fgets(buff, MAX_LOG_LINE_SIZE, fp) != NULL)
+ {
+ printf("%s", buff);
+ }
+ break;
+ }
+ else if (strncmp(buff, g_AllFormatStringFixedPrefix, strlen(g_AllFormatStringFixedPrefix)) == 0)
+ {
+ if (o.applyDiff)
+ {
+ int temp1 = 0, temp2 = 0, temp3 = 0, temp4 = 0;
+ int converted = sscanf(buff, g_AsmDiffsSummaryFormatString, &temp1, &temp2, &temp3, &temp4);
+ if (converted != 4)
+ {
+ LogError("Couldn't parse status message: \"%s\"", buff);
+ }
+ else
+ {
+ *loaded += temp1;
+ *jitted += temp2;
+ *failed += temp3;
+ *diffs += temp4;
+ }
+ }
+ else
+ {
+ int temp1 = 0, temp2 = 0, temp3 = 0;
+ int converted = sscanf(buff, g_SummaryFormatString, &temp1, &temp2, &temp3);
+ if (converted != 3)
+ {
+ LogError("Couldn't parse status message: \"%s\"", buff);
+ }
+ else
+ {
+ *loaded += temp1;
+ *jitted += temp2;
+ *failed += temp3;
+ *diffs = -1;
+ }
+ }
+ }
+ }
+
+Cleanup:
+ if (fp != NULL)
+ fclose(fp);
+}
+
+#ifndef FEATURE_PAL // TODO-Porting: handle Ctrl-C signals gracefully on Unix
+BOOL WINAPI CtrlHandler(DWORD fdwCtrlType)
+{
+ //Since the child SuperPMI.exe processes share the same console
+ //We don't need to kill them individually as they also receive the Ctrl-C
+
+ closeRequested = true; //set a flag to indicate we need to quit
+ return TRUE;
+}
+#endif // !FEATURE_PAL
+
+int __cdecl compareInt(const void *arg1, const void *arg2)
+{
+ return (*(const int *)arg1) - (*(const int *)arg2);
+}
+
+// 'arrWorkerMCLPath' is an array of strings of size 'workerCount'.
+void MergeWorkerMCLs(char *mclFilename, char **arrWorkerMCLPath, int workerCount)
+{
+ int **MCL = new int*[workerCount], *MCLCount = new int[workerCount], totalCount = 0;
+
+ for (int i = 0; i < workerCount; i++)
+ {
+ //Read the next partial MCL file
+ ReadMCLToArray(arrWorkerMCLPath[i], &MCL[i], &MCLCount[i]);
+
+ totalCount += MCLCount[i];
+ }
+
+ int *mergedMCL = new int[totalCount];
+ int index = 0;
+
+ for (int i = 0; i < workerCount; i++)
+ {
+ for (int j = 0; j < MCLCount[i]; j++)
+ mergedMCL[index++] = MCL[i][j];
+ }
+
+ qsort(mergedMCL, totalCount, sizeof(int), compareInt);
+
+ //Write the merged MCL array back to disk
+ if (!WriteArrayToMCL(mclFilename, mergedMCL, totalCount))
+ LogError("Unable to write to MCL file %s.", mclFilename);
+}
+
+// From the arguments that we parsed, construct the arguments to pass to the child processes.
+#define MAX_CMDLINE_SIZE 0x1000 //4 KB
+char* ConstructChildProcessArgs(const CommandLine::Options& o)
+{
+ int bytesWritten = 0;
+ char* spmiArgs = new char[MAX_CMDLINE_SIZE];
+ *spmiArgs = '\0';
+
+ // We don't pass through /parallel, /skipCleanup, /verbosity, /failingMCList, or /diffMCList. Everything else we need to reconstruct and pass through.
+
+#define ADDSTRING(s) if (s != nullptr) { bytesWritten += sprintf_s(spmiArgs + bytesWritten, MAX_CMDLINE_SIZE - bytesWritten, " %s", s); }
+#define ADDARG_BOOL(b,arg) if (b) { bytesWritten += sprintf_s(spmiArgs + bytesWritten, MAX_CMDLINE_SIZE - bytesWritten, " %s", arg); }
+#define ADDARG_STRING(s,arg) if (s != nullptr) { bytesWritten += sprintf_s(spmiArgs + bytesWritten, MAX_CMDLINE_SIZE - bytesWritten, " %s %s", arg, s); }
+
+ ADDARG_BOOL(o.breakOnError, "-boe");
+ ADDARG_BOOL(o.breakOnAssert, "-boa");
+ ADDARG_BOOL(o.applyDiff, "-applyDiff");
+ ADDARG_STRING(o.reproName, "-reproName");
+ ADDARG_STRING(o.writeLogFile, "-writeLogFile");
+ ADDARG_STRING(o.methodStatsTypes, "-emitMethodStats");
+ ADDARG_STRING(o.reproName, "-reproName");
+ ADDARG_STRING(o.hash, "-matchHash");
+ ADDARG_STRING(o.targetArchitecture, "-target");
+ ADDARG_STRING(o.compileList, "-compile");
+
+ ADDSTRING(o.nameOfJit);
+ ADDSTRING(o.nameOfJit2);
+ ADDSTRING(o.nameOfInputMethodContextFile);
+
+#undef ADDSTRING
+#undef ADDARG_BOOL
+#undef ADDARG_STRING
+
+ return spmiArgs;
+}
+
+int doParallelSuperPMI(CommandLine::Options& o)
+{
+ HRESULT hr = E_FAIL;
+ SimpleTimer st;
+ st.Start();
+
+#ifndef FEATURE_PAL // TODO-Porting: handle Ctrl-C signals gracefully on Unix
+ //Register a ConsoleCtrlHandler
+ if (!SetConsoleCtrlHandler(CtrlHandler, TRUE))
+ {
+ LogError("Failed to set control handler.");
+ return 1;
+ }
+#endif // !FEATURE_PAL
+
+ char tempPath[MAX_PATH];
+ if (!GetTempPath(MAX_PATH, tempPath))
+ {
+ LogError("Failed to get path to temp folder.");
+ return 1;
+ }
+
+ if (o.workerCount <= 0)
+ {
+ //Use the default value which is the number of processors on the machine.
+ SYSTEM_INFO sysinfo;
+ GetSystemInfo(&sysinfo);
+
+ o.workerCount = sysinfo.dwNumberOfProcessors;
+
+ //If we ever execute on a machine which has more than MAXIMUM_WAIT_OBJECTS(64) CPU cores
+ //we still can't spawn more than the max supported by WaitForMultipleObjects()
+ if (o.workerCount > MAXIMUM_WAIT_OBJECTS)
+ o.workerCount = MAXIMUM_WAIT_OBJECTS;
+ }
+
+ // Obtain the folder path of the current executable, which we will use to spawn ourself.
+ char* spmiFilename = new char[MAX_PATH];
+ if (!GetModuleFileName(NULL, spmiFilename, MAX_PATH))
+ {
+ LogError("Failed to get current exe path.");
+ return 1;
+ }
+
+ char* spmiArgs = ConstructChildProcessArgs(o);
+
+ // TODO: merge all this output to a single call to LogVerbose to avoid all the newlines.
+ LogVerbose("Using child (%s) with args (%s)", spmiFilename, spmiArgs);
+ if (o.mclFilename != nullptr)
+ LogVerbose(" failingMCList=%s", o.mclFilename);
+ if (o.diffMCLFilename != nullptr)
+ LogVerbose(" diffMCLFilename=%s", o.diffMCLFilename);
+ LogVerbose(" workerCount=%d, skipCleanup=%d.", o.workerCount, o.skipCleanup);
+
+ HANDLE *hProcesses = new HANDLE[o.workerCount];
+ HANDLE *hStdOutput = new HANDLE[o.workerCount];
+ HANDLE *hStdError = new HANDLE[o.workerCount];
+
+ char** arrFailingMCListPath = new char*[o.workerCount];
+ char** arrDiffMCListPath = new char*[o.workerCount];
+ char** arrStdOutputPath = new char*[o.workerCount];
+ char** arrStdErrorPath = new char*[o.workerCount];
+
+ // Add a random number to the temporary file names to allow multiple parallel SuperPMI to happen at once.
+ unsigned int randNumber = 0;
+#ifdef FEATURE_PAL
+ PAL_Random(/* bStrong */ FALSE, &randNumber, sizeof(randNumber));
+#else // !FEATURE_PAL
+ rand_s(&randNumber);
+#endif // !FEATURE_PAL
+
+ for (int i = 0; i < o.workerCount; i++)
+ {
+ if (o.mclFilename != nullptr)
+ {
+ arrFailingMCListPath[i] = new char[MAX_PATH];
+ sprintf_s(arrFailingMCListPath[i], MAX_PATH, "%sParallelSuperPMI-%u-%d.mcl", tempPath, randNumber, i);
+ }
+ else
+ {
+ arrFailingMCListPath[i] = nullptr;
+ }
+
+ if (o.diffMCLFilename != nullptr)
+ {
+ arrDiffMCListPath[i] = new char[MAX_PATH];
+ sprintf_s(arrDiffMCListPath[i], MAX_PATH, "%sParallelSuperPMI-Diff-%u-%d.mcl", tempPath, randNumber, i);
+ }
+ else
+ {
+ arrDiffMCListPath[i] = nullptr;
+ }
+
+ arrStdOutputPath[i] = new char[MAX_PATH];
+ arrStdErrorPath[i] = new char[MAX_PATH];
+
+ sprintf_s(arrStdOutputPath[i], MAX_PATH, "%sParallelSuperPMI-stdout-%u-%d.txt", tempPath, randNumber, i);
+ sprintf_s(arrStdErrorPath[i], MAX_PATH, "%sParallelSuperPMI-stderr-%u-%d.txt", tempPath, randNumber, i);
+ }
+
+ char cmdLine[MAX_CMDLINE_SIZE];
+ cmdLine[0] = '\0';
+ int bytesWritten;
+
+ for (int i = 0; i < o.workerCount; i++)
+ {
+ bytesWritten = sprintf_s(cmdLine, MAX_CMDLINE_SIZE, "%s -stride %d %d", spmiFilename, i + 1, o.workerCount);
+
+ if (o.mclFilename != nullptr)
+ {
+ bytesWritten += sprintf_s(cmdLine + bytesWritten, MAX_CMDLINE_SIZE - bytesWritten, " -failingMCList %s", arrFailingMCListPath[i]);
+ }
+
+ if (o.diffMCLFilename != nullptr)
+ {
+ bytesWritten += sprintf_s(cmdLine + bytesWritten, MAX_CMDLINE_SIZE - bytesWritten, " -diffMCList %s", arrDiffMCListPath[i]);
+ }
+
+ bytesWritten += sprintf_s(cmdLine + bytesWritten, MAX_CMDLINE_SIZE - bytesWritten, " -v ewmin %s", spmiArgs);
+
+ SECURITY_ATTRIBUTES sa;
+ sa.nLength = sizeof(sa);
+ sa.lpSecurityDescriptor = NULL;
+ sa.bInheritHandle = TRUE; // Let newly created stdout/stderr handles be inherited.
+
+ LogDebug("stdout %i=%s", i, arrStdOutputPath[i]);
+ hStdOutput[i] = CreateFileA(arrStdOutputPath[i], GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (hStdOutput[i] == INVALID_HANDLE_VALUE)
+ {
+ LogError("Unable to open '%s'. GetLastError()=%u", arrStdOutputPath[i], GetLastError());
+ return -1;
+ }
+
+ LogDebug("stderr %i=%s", i, arrStdErrorPath[i]);
+ hStdError[i] = CreateFileA(arrStdErrorPath[i], GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (hStdError[i] == INVALID_HANDLE_VALUE)
+ {
+ LogError("Unable to open '%s'. GetLastError()=%u", arrStdErrorPath[i], GetLastError());
+ return -1;
+ }
+
+ //Create a SuperPMI worker process and redirect its output to file
+ if (!StartProcess(cmdLine, hStdOutput[i], hStdError[i], &hProcesses[i]))
+ {
+ return -1;
+ }
+ }
+
+ WaitForMultipleObjects(o.workerCount, hProcesses, true, INFINITE);
+
+ // Close stdout/stderr
+ for (int i = 0; i < o.workerCount; i++)
+ {
+ CloseHandle(hStdOutput[i]);
+ CloseHandle(hStdError[i]);
+ }
+
+ DWORD exitCode = 0; // 0 == assume success
+
+ if (!closeRequested)
+ {
+ // Figure out the error code to use. We use the largest magnitude error code of the children.
+ // Mainly, if any child returns non-zero, we want to return non-zero, to indicate failure.
+ for (int i = 0; i < o.workerCount; i++)
+ {
+ DWORD exitCodeTmp;
+ BOOL ok = GetExitCodeProcess(hProcesses[i], &exitCodeTmp);
+ if (ok && (exitCodeTmp > exitCode))
+ {
+ exitCode = exitCodeTmp;
+ }
+ }
+
+ bool usageError = false; //variable to flag if we hit a usage error in SuperPMI
+
+ int loaded = 0, jitted = 0, failed = 0, diffs = 0;
+
+ //Read the stderr files and log them as errors
+ //Read the stdout files and parse them for counts and log any MISSING or ISSUE errors
+ for (int i = 0; i < o.workerCount; i++)
+ {
+ ProcessChildStdErr(arrStdErrorPath[i]);
+ ProcessChildStdOut(o, arrStdOutputPath[i], &loaded, &jitted, &failed, &diffs, &usageError);
+ if (usageError)
+ break;
+ }
+
+ if (o.mclFilename != nullptr && !usageError)
+ {
+ //Concat the resulting .mcl files
+ MergeWorkerMCLs(o.mclFilename, arrFailingMCListPath, o.workerCount);
+ }
+
+ if (o.diffMCLFilename != nullptr && !usageError)
+ {
+ //Concat the resulting diff .mcl files
+ MergeWorkerMCLs(o.diffMCLFilename, arrDiffMCListPath, o.workerCount);
+ }
+
+ if (!usageError)
+ {
+ if (o.applyDiff)
+ {
+ LogInfo(g_AsmDiffsSummaryFormatString, loaded, jitted, failed, diffs);
+ }
+ else
+ {
+ LogInfo(g_SummaryFormatString, loaded, jitted, failed);
+ }
+ }
+
+ st.Stop();
+ LogVerbose("Total time: %fms", st.GetMilliseconds());
+ }
+
+ if (!o.skipCleanup)
+ {
+ // Delete all temporary files generated
+ for (int i = 0; i < o.workerCount; i++)
+ {
+ if (arrFailingMCListPath[i] != nullptr)
+ {
+ DeleteFile(arrFailingMCListPath[i]);
+ }
+ if (arrDiffMCListPath[i] != nullptr)
+ {
+ DeleteFile(arrDiffMCListPath[i]);
+ }
+ DeleteFile(arrStdOutputPath[i]);
+ DeleteFile(arrStdErrorPath[i]);
+ }
+ }
+
+ return (int)exitCode;
+}