[#5152] Write preprocessed P4 to <program_name>.p4pp file when --save-temps option is provided#5153
[#5152] Write preprocessed P4 to <program_name>.p4pp file when --save-temps option is provided#5153kfcripps wants to merge 6 commits intop4lang:mainfrom
<program_name>.p4pp file when --save-temps option is provided#5153Conversation
|
Not sure why the output should be named
|
Sure, that sounds reasonable to me. |
|
Isn't there already a |
|
@ChrisDodd Yes, but the compiler exits immediately after doing so - the purpose of this option is to be able to save the preprocessed P4 and continue with compilation instead of exiting. |
|
Not sure if it matters, but the existing Tofino implementation names this file as |
|
The other thing I'd strongly recommend if you want to do it in the frontend: sanitize the preprocessed file by not including the standard system includes (i.e. This will allow to reproduce old issues much easier or notice compatibility issues whenever you change your arch files. Believe me, you'd like you did it. |
Giving the generated preprocessed file a
I currently have no strong motivation to do that, but I am not opposed if someone else wants to add an option to do that in the future. |
|
My opinion is that I have no strong opinion about leaving system includes, although having it configurable (maybe with default being "leaving" |
frontends/common/parser_options.cpp
Outdated
| newName += baseSuffix; | ||
| newName += name.extension(); | ||
| if (savePreprocessed) { | ||
| std::filesystem::path fileName = makeFileName(dumpFolder, "repro.p4", ""); |
There was a problem hiding this comment.
I think that using <program_name>.p4pp would be better, compared to having a fairly arbitrary fixed name.
| /// if true preprocess only | ||
| bool doNotCompile = false; | ||
| /// if true save preprocessed P4 to repro.p4 | ||
| bool savePreprocessed = false; |
There was a problem hiding this comment.
If you decide to address my first comment, then, obviously, this comment needs to be changed too.
|
Updates:
|
repro.p4 file when -P option is provided<program_name>.p4pp file when --save-temps option is provided
vlstill
left a comment
There was a problem hiding this comment.
I think the double preprocessing is a bit unfortunate (it is wasteful and in an extreme case, they could produce different results because of changes on the filesystem). Maybe instead if we have --save-temps we should direct the preprocessor output to the p4pp file and then open that file for the parser.
| #ifdef __clang__ | ||
| std::string cmd("cc -E -x c -Wno-comment"); | ||
| cmd = "cc -E -x c -Wno-comment"; | ||
| #else | ||
| std::string cmd("cpp"); | ||
| cmd = "cpp"; | ||
| #endif |
There was a problem hiding this comment.
Why was the cmd var moved outside the if? When you are already touching this, I suggest converting it to a plain C++ if. Both branches are valid C++ after all. It could even be a ternary op: std::string cmd = (__clang__) ? ... : ...; (I would add the extra brackets because it is a macro, but maybe formatter will not like them).
There was a problem hiding this comment.
I moved the cmd declaration back inside the #ifdef, but I'm not sure that macros can be used inside of regular if statements / ternary operations. When I tried doing this, I got the error:
../../../frontends/common/parser_options.cpp: In member function 'std::optional<std::unique_ptr<_IO_FILE, void (*)(_IO_FILE*)> > P4::ParserOptions::preprocess() const':
../../../frontends/common/parser_options.cpp:435:27: error: '__clang__' was not declared in this scope
435 | std::string cmd = __clang__ ? "cc -E -x c -Wno-comment" : "cpp";
| ^~~~~~~~~
ninja: build stopped: subcommand failed.
This does seem better to me too. Done. |
Signed-off-by: Kyle Cripps <kyle@pensando.io>
…program.p4pp. Signed-off-by: Kyle Cripps <kyle@pensando.io>
Signed-off-by: kfcripps <kyle@pensando.io>
Signed-off-by: kfcripps <kyle@pensando.io>
Signed-off-by: Kyle Cripps <kyle@pensando.io>
Signed-off-by: Kyle Cripps <60898032+kfcripps@users.noreply.github.com>
There was a problem hiding this comment.
Pull Request Overview
This PR implements the --save-temps option to save preprocessed P4 code to a <program_name>.p4pp file without exiting compilation, addressing issue #5152.
Key Changes:
- Adds
--save-tempscommand-line option to save preprocessed output - Implements logic to write preprocessed content to a
.p4ppfile and continue compilation - Updates help text across test output files to document the new option
Reviewed Changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
frontends/common/parser_options.h |
Adds savePreprocessed boolean flag to track when the option is enabled |
frontends/common/parser_options.cpp |
Implements --save-temps option registration and preprocessing logic to save and reopen the preprocessed file |
testdata/p4_16_errors_outputs/issue4365_no_disable_error.p4-stderr |
Updates help text to include the new --save-temps option |
testdata/p4_16_errors_outputs/issue4365_no_demote_error_to_warning.p4-stderr |
Updates help text to include the new --save-temps option |
testdata/p4_16_errors_outputs/issue4365_no_demote_error_to_info.p4-stderr |
Updates help text to include the new --save-temps option |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| in = fopen(fileName.c_str(), "r"); | ||
| if (in == nullptr) { | ||
| ::P4::error(ErrorType::ERR_IO, "Error invoking preprocessor"); |
There was a problem hiding this comment.
The error message "Error invoking preprocessor" is misleading here since the preprocessor already succeeded. The actual error is that the saved preprocessed file cannot be reopened for reading. Consider a more accurate error message like "Error opening preprocessed file for reading" or "Failed to reopen saved preprocessed file".
| ::P4::error(ErrorType::ERR_IO, "Error invoking preprocessor"); | |
| ::P4::error(ErrorType::ERR_IO, "Error opening preprocessed file for reading"); |
| savePreprocessed = true; | ||
| return true; | ||
| }, | ||
| "Saves preprocessed P4 to filename.p4pp and do not exit compilation."); |
There was a problem hiding this comment.
Grammatical error: "do not exit compilation" should be "does not exit compilation" to match the singular subject of the sentence.
| "Saves preprocessed P4 to filename.p4pp and do not exit compilation."); | |
| "Saves preprocessed P4 to filename.p4pp and does not exit compilation."); |
| -U arg Undefine macro (passed to preprocessor) | ||
| -E Preprocess only, do not compile (prints program on stdout) | ||
| -M Output `make` dependency rule only (passed to preprocessor) | ||
| --save-temps Saves preprocessed P4 to filename.p4pp and do not exit compilation. |
There was a problem hiding this comment.
Grammatical error: "do not exit compilation" should be "does not exit compilation" to match the singular subject of the sentence.
| --save-temps Saves preprocessed P4 to filename.p4pp and do not exit compilation. | |
| --save-temps Saves preprocessed P4 to filename.p4pp and does not exit compilation. |
| std::filesystem::path fileName(file.stem()); | ||
| fileName += ".p4pp"; | ||
| fileName = makeFileName(dumpFolder, fileName, ""); |
There was a problem hiding this comment.
[nitpick] The code unnecessarily constructs fileName in lines 475-476 only to pass it to makeFileName which deconstructs and reconstructs it. This can be simplified by calling makeFileName(dumpFolder, file, ".p4pp") directly, which would be clearer and avoid the intermediate construction.
| std::filesystem::path fileName(file.stem()); | |
| fileName += ".p4pp"; | |
| fileName = makeFileName(dumpFolder, fileName, ""); | |
| std::filesystem::path fileName = makeFileName(dumpFolder, file, ".p4pp"); |
| std::stringstream stream; | ||
| char *line = nullptr; | ||
| size_t len = 0; | ||
| ssize_t read = 0; |
There was a problem hiding this comment.
[nitpick] Unused variable: The variable read is assigned but never used. The return value of getline is only needed to check for end-of-file, so you can remove the variable declaration and check the condition directly: while (getline(&line, &len, in) != -1).
| -U arg Undefine macro (passed to preprocessor) | ||
| -E Preprocess only, do not compile (prints program on stdout) | ||
| -M Output `make` dependency rule only (passed to preprocessor) | ||
| --save-temps Saves preprocessed P4 to filename.p4pp and do not exit compilation. |
There was a problem hiding this comment.
Grammatical error: "do not exit compilation" should be "does not exit compilation" to match the singular subject of the sentence.
| --save-temps Saves preprocessed P4 to filename.p4pp and do not exit compilation. | |
| --save-temps Saves preprocessed P4 to filename.p4pp and does not exit compilation. |
| -U arg Undefine macro (passed to preprocessor) | ||
| -E Preprocess only, do not compile (prints program on stdout) | ||
| -M Output `make` dependency rule only (passed to preprocessor) | ||
| --save-temps Saves preprocessed P4 to filename.p4pp and do not exit compilation. |
There was a problem hiding this comment.
Grammatical error: "do not exit compilation" should be "does not exit compilation" to match the singular subject of the sentence.
| --save-temps Saves preprocessed P4 to filename.p4pp and do not exit compilation. | |
| --save-temps Saves preprocessed P4 to filename.p4pp and does not exit compilation. |
| std::ofstream filestream{fileName}; | ||
| if (filestream) { | ||
| if (Log::verbose()) std::cerr << "Writing preprocessed P4 to " << fileName << std::endl; | ||
| filestream << stream.str(); |
There was a problem hiding this comment.
Missing error handling for file write failure. If the file write fails (e.g., due to disk space issues or permission problems), the code silently continues. Consider adding error reporting if !filestream after opening or if the write operation fails.
| filestream << stream.str(); | |
| filestream << stream.str(); | |
| if (!filestream) { | |
| ::P4::error(ErrorType::ERR_IO, "Failed to write preprocessed P4 to %s", fileName.c_str()); | |
| perror(""); | |
| filestream.close(); | |
| return std::nullopt; | |
| } |
| char *line = nullptr; | ||
| size_t len = 0; | ||
| ssize_t read = 0; | ||
|
|
||
| return folder / newName; | ||
| while ((read = getline(&line, &len, in)) != -1) { | ||
| stream << line; | ||
| } |
There was a problem hiding this comment.
Memory leak: The line buffer allocated by getline is never freed. According to POSIX standards, getline allocates memory that must be freed by the caller. Add free(line) after the while loop to prevent memory leaks.
| while ((read = getline(&line, &len, in)) != -1) { | ||
| stream << line; | ||
| } | ||
| closeFile(in); |
There was a problem hiding this comment.
Bug: Calling closeFile(in) on stdin will cause issues. When file == "-", in is set to stdin (line 433), but closeFile calls pclose() which should only be used on streams opened with popen(). This will result in undefined behavior. The code should check if in == stdin before calling closeFile(in), or handle stdin differently.
Closes #5152.
Marking as draft until we decide:
-P(for "Preprocessed") for now, but @asl also suggested--save-temps.repro.p4(short for "reproducer"), but maybe there is a better name.