blob: f8aeb0bd88d1211a7a28c36931e7f6de7adc7f15 [file] [log] [blame]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
// rlregr.c
//
// assembly regression worker for rl.c
#include "rl.h"
#define TMP_PREFIX "di" // 2 characters
// Currently not overridable
#define REGR_PERL "perl"
#define DIR_START_CMD "startdir.cmd"
#define DIR_END_CMD "enddir.cmd"
// <rl> ... </rl> directives:
//
// PassFo
// Doesn't add -FoNUL. to the command line; allows passing of -Fo
// explicitly.
//
// NoAsm
// Disables asm file generation.
#define PASS_FO_CMD "PassFo"
#define NO_ASM_CMD "NoAsm"
//
// Global variables set before worker threads start, and only accessed
// (not set) by the worker threads.
//
static BOOL NoMasterCompare;
LOCAL int
DoCommand(
const char *path, // full path of directory to run command in
const char *cmdbuf,
bool displayError = true
)
{
int rtncode;
rtncode = ExecuteCommand(path, cmdbuf);
if (rtncode && displayError) {
LogOut("ERROR: Command had a return code of %d:", rtncode);
LogOut(" %s", cmdbuf);
}
return rtncode;
}
LOCAL void __cdecl
RegrCleanUp(void)
{
}
// One-time assembly regression initialization.
void
RegrInit(
void
)
{
atexit(RegrCleanUp);
if (getenv_unsafe("REGR_NOMASTERCMP") != NULL) {
if (FBaseline) {
Fatal("-baseline conflicts with environment variable REGR_NOMASTERCMP.");
}
NoMasterCompare = TRUE;
}
else {
NoMasterCompare = FALSE;
}
}
// Called at the start of each directory.
BOOL
RegrStartDir(
char *path
)
{
char full[MAX_PATH];
// Execute directory setup command script if present.
sprintf_s(full, "%s\\%s", path, DIR_START_CMD);
if (GetFileAttributes(full) != INVALID_FILE_ATTRIBUTES) {
Message(""); // newline
Message("Executing %s", DIR_START_CMD);
if (DoCommand(path, DIR_START_CMD))
return FALSE;
Message(""); // newline
}
return TRUE;
}
// Called at the end of each directory.
BOOL
RegrEndDir(
char* path
)
{
char full[MAX_PATH];
// Execute directory shutdown command script if present.
sprintf_s(full, "%s\\%s", path, DIR_END_CMD);
if (GetFileAttributes(full) != INVALID_FILE_ATTRIBUTES) {
Message(""); // newline
Message("Executing %s", DIR_END_CMD);
if (DoCommand(path, DIR_END_CMD))
return FALSE;
Message("");
}
return TRUE;
}
// Called to perform assembly regressions on one file.
// Returns -1 for failure, 1 for diff, 0 for okay.
int
RegrFile(
CDirectory* pDir,
Test * pTest,
TestVariant * pTestVariant
)
{
FILE *fp;
char *p;
const char *opts;
int x;
int rc;
int retval;
char full[MAX_PATH]; // temporary place for full paths
char basename[MAX_PATH]; // base filename
const char *asmdir; // dir of generated asm file
char masterasmbuf[MAX_PATH]; // name of master asm file
char asmbuf[MAX_PATH]; // name of generated asm file
char diffbuf[MAX_PATH]; // name of generated diff file
char objbuf[MAX_PATH]; // name of generated obj file
char fullmasterasmbuf[MAX_PATH]; // path of master asm file
char fullasmbuf[MAX_PATH]; // path of generated asm file
char fulldiffbuf[MAX_PATH]; // path of generated diff file
char fullobjbuf[MAX_PATH]; // path of generated obj file
char standardoptbuf[BUFFER_SIZE]; // standard compilation options (-Fa..)
char cmdbuf[BUFFER_SIZE]; // compilation command
char compilerbuf[BUFFER_SIZE]; // compiler name buffer
char buf[BUFFER_SIZE];
char tmp_file1[BUFFER_SIZE];
char tmp_file2[BUFFER_SIZE];
char tmp_file3[BUFFER_SIZE];
BOOL passFo = FALSE;
BOOL noAsm = FALSE;
// We don't allow non-target conditions for ASM currently, so we should
// never see a conditionNodeList.
ASSERTNR(pTest->conditionNodeList == NULL);
// Check the directives.
if (HasInfo(pTest->defaultTestInfo.data[TIK_RL_DIRECTIVES], XML_DELIM,
PASS_FO_CMD))
{
passFo = TRUE;
}
if (HasInfo(pTest->defaultTestInfo.data[TIK_RL_DIRECTIVES], XML_DELIM,
NO_ASM_CMD))
{
noAsm = TRUE;
}
// Base file name (name.* -> name)
strcpy_s(basename, pTest->name);
for(p = basename; *p != '.' && *p != '\0'; p++);
*p = '\0';
// Determine the standard options.
asmdir = pDir->IsBaseline() ? MASTER_DIR : DIFF_DIR;
sprintf_s(standardoptbuf, "-AsmDumpMode:%s\\%s.asm",
asmdir, basename);
switch (TargetMachine) {
case TM_WVM:
case TM_WVMX86:
case TM_WVM64:
strcat_s(standardoptbuf, " /BC ");
break;
}
if (BaseCompiler || DiffCompiler) {
char *b2;
if (pDir->IsBaseline())
b2 = BaseCompiler;
else
b2 = DiffCompiler;
ASSERTNR(b2);
sprintf_s(compilerbuf, " -B2 %s", b2);
}
else
{
*compilerbuf = '\0';
}
// Try to open the source file.
sprintf_s(full, "%s\\%s", pDir->GetDirectoryPath(), pTest->name);
fp = fopen_unsafe(full, "r");
if (fp == NULL) {
LogError("ERROR: Could not open '%s' with error '%s'", pTest->name, strerror_unsafe(errno));
return -1;
}
fclose(fp);
ASSERTNR(pTestVariant->optFlags == NULL);
ASSERTNR(pTestVariant->testInfo.data[TIK_LINK_FLAGS] == NULL);
opts = pTestVariant->testInfo.data[TIK_COMPILE_FLAGS];
if (opts == NULL)
{
opts = "";
}
// Compute the object name if passing -Fo through.
if (passFo)
sprintf_s(objbuf, "%s.obj", basename);
else
sprintf_s(objbuf, "%s\\%s.obj", asmdir, basename);
sprintf_s(fullobjbuf, "%s\\%s", pDir->GetDirectoryPath(), objbuf);
// Compute the names for the assembly and diff files.
sprintf_s(masterasmbuf, "%s\\%s.asm", MASTER_DIR, basename);
sprintf_s(asmbuf, "%s\\%s.asm", asmdir, basename);
sprintf_s(diffbuf, "%s\\%s.d", DIFF_DIR, basename);
sprintf_s(fullmasterasmbuf, "%s\\%s", pDir->GetDirectoryPath(), masterasmbuf);
sprintf_s(fullasmbuf, "%s\\%s", pDir->GetDirectoryPath(), asmbuf);
sprintf_s(fulldiffbuf, "%s\\%s", pDir->GetDirectoryPath(), diffbuf);
// There could be a diff file from previous regressions. Remove it now.
if (!pDir->IsBaseline())
DeleteFileIfFound(fulldiffbuf);
// Create the compiler command and do the compile.
sprintf_s(cmdbuf, "%s%s %s %s %s %s 2>nul 1>nul",
JCBinary, compilerbuf, standardoptbuf, opts, EXTRA_CC_FLAGS,
pTest->name);
if (FVerbose) {
strcpy_s(buf, cmdbuf);
}
else {
// For non-verbose, don't display the standard options
sprintf_s(buf, "%s%s %s %s %s",
JCBinary, compilerbuf, opts, EXTRA_CC_FLAGS, pTest->name);
}
Message("%s", buf);
if (FTest)
{
return 0;
}
// There could be a diff file from previous regressions. Remove it now.
if (!pDir->IsBaseline())
{
DeleteFileIfFound(fulldiffbuf);
}
if (DoCommand(pDir->GetDirectoryPath(), cmdbuf))
{
DeleteFileIfFound(fullasmbuf);
return -1;
}
// If we are passing -Fo through to the compiler, remove the generated
// object file.
if (passFo)
DeleteFileRetryMsg(fullobjbuf);
// See if we generated an assembly file.
if (GetFileAttributes(fullasmbuf) == INVALID_FILE_ATTRIBUTES) {
LogOut("ERROR: Assembly file %s not generated", asmbuf);
return -1;
}
// Create the assembler command and do the asm if necessary.
if (REGR_ASM && !noAsm) {
sprintf_s(cmdbuf, "%s %s", REGR_ASM, asmbuf);
Message("%s", cmdbuf);
// ignore the REGR_ASM return code; we still want to do diffs or
// update the master with the generated asm file.
if (DoCommand(pDir->GetDirectoryPath(), cmdbuf)) {
retval = -1;
}
else {
retval = 0;
DeleteFileRetryMsg(fullobjbuf);
}
} else {
retval = 0;
}
// Exit if not comparing to master or doing baselines.
if (NoMasterCompare || pDir->IsBaseline())
return retval;
// FYI: Up to this point we haven't used fullmasterasmbuf.
ASSERTNR(!strcmp(asmdir, DIFF_DIR));
// If the master doesn't exist, consider this a failure.
if (GetFileAttributes(fullmasterasmbuf) == INVALID_FILE_ATTRIBUTES) {
LogOut("ERROR: %s doesn't exist", fullmasterasmbuf);
return -1;
}
x = DoCompare(fullasmbuf, fullmasterasmbuf, pTestVariant->testInfo.hasData[TIK_EOL_NORMALIZATION]);
if (x < 0)
return -1;
// If the files differ, remove potential bogus differences and try again.
rc = retval;
if (x) {
// Remove the possible bogus diffs.
// Generate some temporary file names.
// Note: these are full pathnames, not relative pathnames.
// NOTE: BE VERY CAREFUL TO CLEAN THESE UP!
if (mytmpnam(pDir->GetDirectoryPath(), TMP_PREFIX, tmp_file1) == NULL ||
mytmpnam(pDir->GetDirectoryPath(), TMP_PREFIX, tmp_file2) == NULL ||
mytmpnam(pDir->GetDirectoryPath(), TMP_PREFIX, tmp_file3) == NULL) {
Fatal("Unable to create temporary files");
}
ThreadInfo[ThreadId].AddToTmpFileList(tmp_file1);
ThreadInfo[ThreadId].AddToTmpFileList(tmp_file2);
ThreadInfo[ThreadId].AddToTmpFileList(tmp_file3);
sprintf_s(cmdbuf, "%s %s %s %s",
REGR_SHOWD, REGR_PERL, fullasmbuf, tmp_file1);
if (DoCommand(pDir->GetDirectoryPath(), cmdbuf)) {
rc = -1;
}
else {
sprintf_s(cmdbuf, "%s %s %s %s",
REGR_SHOWD, REGR_PERL, fullmasterasmbuf, tmp_file2);
if (DoCommand(pDir->GetDirectoryPath(), cmdbuf)) {
rc = -1;
}
else {
// Compare again.
x = DoCompare(tmp_file1, tmp_file2, pTestVariant->testInfo.hasData[TIK_EOL_NORMALIZATION]);
if (x < 0) {
rc = -1;
}
else if (x) {
// They still differ, make a diff file.
sprintf_s(cmdbuf, "%s %s %s > %s",
REGR_DIFF, tmp_file1, tmp_file2, tmp_file3);
DoCommand(pDir->GetDirectoryPath(), cmdbuf, false);
sprintf_s(cmdbuf,"%s -p -e \"s/^[<>-].*\\r?\\n//\" < %s > %s",
REGR_PERL, tmp_file3, fulldiffbuf);
if (DoCommand(pDir->GetDirectoryPath(), cmdbuf)) {
rc = -1;
}
else {
Message("%s and %s differ", asmbuf, masterasmbuf);
WriteLog("%s has diffs", pTest->name);
rc = 1;
}
}
else {
// Move the new asm into the master directory
DeleteFileRetryMsg(fullmasterasmbuf);
if (!MoveFile(fullasmbuf, fullmasterasmbuf)) {
LogOut("ERROR: MoveFile(%s, %s) failed with error %d",
fullasmbuf, fullmasterasmbuf, GetLastError());
rc = -1;
}
}
}
}
ThreadInfo[ThreadId].DeleteTmpFileList();
}
else {
// They don't differ, so delete the assembly file we just created.
DeleteFileRetryMsg(fullasmbuf);
}
return rc;
}