From 3c57c86cdb2ae301441cf26a5bbe137e0b3bd512 Mon Sep 17 00:00:00 2001 From: jsmall-nvidia Date: Thu, 24 Oct 2019 21:14:12 -0400 Subject: * Functionality to dump repo if there is a failure throught the -dump-repro-on-failure option (#1095) * Small typo fix --- docs/repro.md | 3 +- source/slang/slang-compiler.h | 3 ++ source/slang/slang-diagnostic-defs.h | 3 +- source/slang/slang-options.cpp | 4 ++ source/slang/slang-state-serialize.cpp | 74 +++++++++++++++++++++++++++++++++- source/slang/slang-state-serialize.h | 3 ++ source/slang/slang.cpp | 39 ++++++++++++++---- 7 files changed, 118 insertions(+), 11 deletions(-) diff --git a/docs/repro.md b/docs/repro.md index cbdd88c2f..5e9070640 100644 --- a/docs/repro.md +++ b/docs/repro.md @@ -7,11 +7,12 @@ One use of the feature is if a compilation fails, or produces an unexpected or w The actual data saved is the contents of the SlangCompileReqest. Currently no state is saved from the SlangSession. Saving and loading a SlangCompileRequest into a new SlangCompileRequest should provide two SlangCompileRequests with the same state, and with the second compile request having access to all the files contents the original request had directly in memory. -There are 3 command line options +There are a few command line options * `-dump-repro [filename]` dumps the compilations state (ie post attempting to compile) to the file specified afterwards * `-extract-repro [filename]` extracts the contents of the repro file. The contained files are placed in a directory with a name, the same as the repro file minus the extension. Also contains a 'manifest'. * `-load-repro [filename]` loads the repro and compiles using it's options. Note this must be the last arg on the command line. +* `-dump-repro-on-error` if a compilation fails will attempt to save a repro (using a filename generated from first source filename) First it is worth just describing what is required to reproduce a compilation. Most straightforwardly the options setup for the compilation need to be stored. This would include any flags, and defines, include paths, entry points, input filenames and so forth. Also needed will be the contents of any files that were specified. This might be files on the file system, but could also be 'files' specified as strings through the slang API. Lastly we need any files that were referenced as part of the compilation - this could be include files, or module source files and so forth. All of this information is bundled up together into a file that can then later be loaded and compiled. This is broadly speaking all of the data that is stored within a repro file. diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h index a5a1b7d93..1900342da 100644 --- a/source/slang/slang-compiler.h +++ b/source/slang/slang-compiler.h @@ -1728,6 +1728,9 @@ namespace Slang // If set, will dump the compilation state String dumpRepro; + /// If set, if a compilation failure occurs will attempt to save off a dump repro with a unique name + bool dumpReproOnError = false; + /// A blob holding the diagnostic output ComPtr diagnosticOutputBlob; diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index 008b1ef04..3a708712b 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -108,7 +108,8 @@ DIAGNOSTIC( 70, Error, cannotMatchOutputFileToEntryPoint, "the output path '$ DIAGNOSTIC( 80, Error, duplicateOutputPathsForEntryPointAndTarget, "multiple output paths have been specified entry point '$0' on target '$1'") -DIAGNOSTIC( 81, Error, parametersAfterLoadReproIgnored, "Parameters after -load-repro [file] are ignored") +DIAGNOSTIC( 81, Error, parametersAfterLoadReproIgnored, "parameters after -load-repro [file] are ignored") +DIAGNOSTIC( 82, Error, unableToWriteReproFile, "unable to write repro file '%0'"); // // 1xxxx - Lexical anaylsis diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp index c76993ca4..e9a8e7e04 100644 --- a/source/slang/slang-options.cpp +++ b/source/slang/slang-options.cpp @@ -498,6 +498,10 @@ struct OptionsParser SLANG_RETURN_ON_FAIL(tryReadCommandLineArgument(sink, arg, &argCursor, argEnd, requestImpl->dumpRepro)); spEnableReproCapture(asExternal(requestImpl)); } + else if (argStr == "-dump-repro-on-error") + { + requestImpl->dumpReproOnError = true; + } else if (argStr == "-extract-repro") { String reproName; diff --git a/source/slang/slang-state-serialize.cpp b/source/slang/slang-state-serialize.cpp index 2d40e78ef..4c2651475 100644 --- a/source/slang/slang-state-serialize.cpp +++ b/source/slang/slang-state-serialize.cpp @@ -3,6 +3,8 @@ #include "../core/slang-text-io.h" +#include "../core/slang-stream.h" + #include "../core/slang-math.h" #include "slang-source-loc.h" @@ -1166,8 +1168,6 @@ struct LoadContext case CompressedResult::CannotOpen: builder << "[cannot open]"; break; case CompressedResult::Fail: builder << "[fail]"; break; } - - } builder << "\n"; @@ -1177,4 +1177,74 @@ struct LoadContext return SLANG_OK; } +static SlangResult _findFirstSourcePath(EndToEndCompileRequest* request, String& outFilename) +{ + // We are going to look through all of the srcTranlationUnits, looking for the first filename + + auto frontEndReq = request->getFrontEndReq(); + const auto& srcTranslationUnits = frontEndReq->translationUnits; + + for (Index i = 0; i < srcTranslationUnits.getCount(); ++i) + { + TranslationUnitRequest* srcTranslationUnit = srcTranslationUnits[i]; + const auto& srcSourceFiles = srcTranslationUnit->getSourceFiles(); + + for (Index j = 0; j < srcSourceFiles.getCount(); ++j) + { + SourceFile* sourceFile = srcSourceFiles[j]; + + const PathInfo& pathInfo = sourceFile->getPathInfo(); + + if (pathInfo.foundPath.getLength()) + { + outFilename = pathInfo.foundPath; + return SLANG_OK; + } + } + } + return SLANG_FAIL; +} + +/* static */SlangResult StateSerializeUtil::findUniqueReproDumpStream(EndToEndCompileRequest* request, String& outFileName, RefPtr& outStream) +{ + String sourcePath; + + if (SLANG_FAILED(_findFirstSourcePath(request, sourcePath))) + { + sourcePath = "unknown.slang"; + } + + String sourceFileName = Path::getFileName(sourcePath); + String sourceBaseName = Path::getFileNameWithoutExt(sourceFileName); + + // Okay we need a unique number to make sure the name is unique + const int maxTries = 100; + for (int triesCount = 0; triesCount < maxTries; ++triesCount) + { + // We could include the count in some way perhaps, but for now let's just go with ticks + auto tick = ProcessUtil::getClockTick(); + + StringBuilder builder; + builder << sourceBaseName << "-" << tick << ".slang-repro"; + + // We write out the file name tried even if it fails, as might be useful in reporting + outFileName = builder; + + // We could have clashes, as we use ticks, we should get to a point where the clashes stop + try + { + outStream = new FileStream(builder, FileMode::CreateNew, FileAccess::Write, FileShare::WriteOnly); + return SLANG_OK; + } + catch (IOException&) + { + } + + // TODO(JS): + // Might make sense to sleep here - but don't seem to have cross platform func for that yet. + } + + return SLANG_FAIL; +} + } // namespace Slang diff --git a/source/slang/slang-state-serialize.h b/source/slang/slang-state-serialize.h index 6de792097..d02bfae8c 100644 --- a/source/slang/slang-state-serialize.h +++ b/source/slang/slang-state-serialize.h @@ -180,6 +180,9 @@ struct StateSerializeUtil /// Given the repo file work out a suitable path static SlangResult calcDirectoryPathFromFilename(const String& filename, String& outPath); + + /// Given a request trys to determine a suitable dump file name, that is unique. + static SlangResult findUniqueReproDumpStream(EndToEndCompileRequest* request, String& outFileName, RefPtr& outStream); }; } // namespace Slang diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index 3053b60c0..29b46c805 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -3214,7 +3214,8 @@ SLANG_API SlangResult spSetTypeNameForEntryPointExistentialTypeParam( SLANG_API SlangResult spCompile( SlangCompileRequest* request) { - auto req = Slang::asInternal(request); + using namespace Slang; + auto req = asInternal(request); SlangResult res = SLANG_FAIL; @@ -3233,19 +3234,19 @@ SLANG_API SlangResult spCompile( { res = req->executeActions(); } - catch (Slang::AbortCompilationException&) + catch (AbortCompilationException&) { // This situation indicates a fatal (but not necessarily internal) error // that forced compilation to terminate. There should already have been // a diagnostic produced, so we don't need to add one here. } - catch (Slang::Exception& e) + catch (Exception& e) { // The compiler failed due to an internal error that was detected. // We will print out information on the exception to help out the user // in either filing a bug, or locating what in their code created // a problem. - req->getSink()->diagnose(Slang::SourceLoc(), Slang::Diagnostics::compilationAbortedDueToException, typeid(e).name(), e.Message); + req->getSink()->diagnose(SourceLoc(), Diagnostics::compilationAbortedDueToException, typeid(e).name(), e.Message); } catch (...) { @@ -3253,7 +3254,7 @@ SLANG_API SlangResult spCompile( // `Exception`, so something really fishy is going on. We want to // let the user know that we messed up, so they know to blame Slang // and not some other component in their system. - req->getSink()->diagnose(Slang::SourceLoc(), Slang::Diagnostics::compilationAborted); + req->getSink()->diagnose(SourceLoc(), Diagnostics::compilationAborted); } req->mDiagnosticOutput = req->getSink()->outputBuffer.ProduceString(); @@ -3265,9 +3266,33 @@ SLANG_API SlangResult spCompile( } #endif - if (req->dumpRepro.getLength()) + // Repro dump handling { - SLANG_RETURN_ON_FAIL(Slang::StateSerializeUtil::saveState(req, req->dumpRepro)); + if (req->dumpRepro.getLength()) + { + SlangResult saveRes = StateSerializeUtil::saveState(req, req->dumpRepro); + if (SLANG_FAILED(saveRes)) + { + req->getSink()->diagnose(SourceLoc(), Diagnostics::unableToWriteReproFile, req->dumpRepro); + return saveRes; + } + } + else if (req->dumpReproOnError && SLANG_FAILED(res)) + { + String reproFileName; + SlangResult saveRes = SLANG_FAIL; + + RefPtr stream; + if (SLANG_SUCCEEDED(StateSerializeUtil::findUniqueReproDumpStream(req, reproFileName, stream))) + { + saveRes = StateSerializeUtil::saveState(req, stream); + } + + if (SLANG_FAILED(saveRes)) + { + req->getSink()->diagnose(SourceLoc(), Diagnostics::unableToWriteReproFile, reproFileName); + } + } } return res; -- cgit v1.2.3