Skip to content

Commit

Permalink
P4Fmt reference checker (#4778)
Browse files Browse the repository at this point in the history
* P4Fmt reference checker

Signed-off-by: Nitish <[email protected]>

* add: sample & ref p4 files

Signed-off-by: Nitish <[email protected]>

* restructure p4fmt to a reusable library

Signed-off-by: Nitish <[email protected]>

* add missing files

Signed-off-by: Nitish <[email protected]>

* update header guard

Signed-off-by: Nitish <[email protected]>

* replace `std::cerr` with ::error(...)

Signed-off-by: Nitish <[email protected]>

* update README with `checkfmt`

Signed-off-by: Nitish <[email protected]>

* move descriptive comment to the header file

Signed-off-by: Nitish <[email protected]>

---------

Signed-off-by: Nitish <[email protected]>
  • Loading branch information
snapdgn authored Jul 9, 2024
1 parent 61184c5 commit e135f41
Show file tree
Hide file tree
Showing 8 changed files with 313 additions and 32 deletions.
18 changes: 17 additions & 1 deletion backends/p4fmt/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
set(FMT_SRCS
p4fmt.cpp
options.cpp
main.cpp
)

set(REFCHECK_SRCS
refcheck.cpp
options.cpp
p4fmt.cpp
)

# p4fmt
add_executable(p4fmt ${FMT_SRCS})
target_link_libraries(p4fmt ${P4C_LIBRARIES} ${P4C_LIB_DEPS})
add_dependencies(p4fmt frontend)
Expand All @@ -11,4 +19,12 @@ add_custom_target(p4formatter
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_BINARY_DIR}/p4fmt ${P4C_BINARY_DIR}/p4fmt
)

add_dependencies(p4c_driver p4formatter)
# p4fmt - reference checker
add_executable(checkfmt ${REFCHECK_SRCS})
target_link_libraries(checkfmt ${P4C_LIBRARIES} ${P4C_LIB_DEPS})

add_custom_target(p4refchecker
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_BINARY_DIR}/checkfmt ${P4C_BINARY_DIR}/checkfmt
)

add_dependencies(p4c_driver p4formatter p4refchecker)
9 changes: 7 additions & 2 deletions backends/p4fmt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,10 @@ Later `p4fmt` executable can be found inside the `p4c/build/` dir, and can be in

Sample Usage:

./build/p4fmt/p4fmt sample.p4
./build/p4fmt/p4fmt sample.p4 -o out.p4
./build/p4fmtsample.p4
./build/p4fmt sample.p4 -o out.p4

## Reference Checker for P4Fmt

Sample Usage:
`./build/checkfmt --file <p4 source file> --reference-file <p4 reference file>`
44 changes: 44 additions & 0 deletions backends/p4fmt/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include <cstdlib>
#include <filesystem>
#include <sstream>

#include "lib/nullstream.h"
#include "options.h"
#include "p4fmt.h"

int main(int argc, char *const argv[]) {
AutoCompileContext autoP4FmtContext(new P4Fmt::P4FmtContext);
auto &options = P4Fmt::P4FmtContext::get().options();
if (options.process(argc, argv) == nullptr) {
return EXIT_FAILURE;
}
options.setInputFile();

std::stringstream formattedOutput = getFormattedOutput(options.file);
if (formattedOutput.str().empty()) {
return EXIT_FAILURE;
};

std::ostream *out = nullptr;
// Write to stdout in absence of an output file.
if (options.outputFile().empty()) {
out = &std::cout;
} else {
out = openFile(options.outputFile(), false);
if ((out == nullptr) || !(*out)) {
::error(ErrorType::ERR_NOT_FOUND, "%2%: No such file or directory.",
options.outputFile().string());
options.usage();
return EXIT_FAILURE;
}
}

(*out) << formattedOutput.str();
out->flush();
if (!(*out)) {
::error(ErrorType::ERR_IO, "Failed to write to output file.");
return EXIT_FAILURE;
}

return EXIT_SUCCESS;
}
42 changes: 13 additions & 29 deletions backends/p4fmt/p4fmt.cpp
Original file line number Diff line number Diff line change
@@ -1,51 +1,35 @@
#include <cstdlib>
#include "backends/p4fmt/p4fmt.h"

#include "frontends/common/parseInput.h"
#include "frontends/common/parser_options.h"
#include "frontends/p4/toP4/toP4.h"
#include "ir/ir.h"
#include "lib/compile_context.h"
#include "lib/cstring.h"
#include "lib/error.h"
#include "lib/nullstream.h"
#include "options.h"

int main(int argc, char *const argv[]) {
std::stringstream getFormattedOutput(std::filesystem::path inputFile) {
AutoCompileContext autoP4FmtContext(new P4Fmt::P4FmtContext);
auto &options = P4Fmt::P4FmtContext::get().options();

if (options.process(argc, argv) == nullptr) {
return EXIT_FAILURE;
}
options.file = std::move(inputFile);

options.setInputFile();

std::ostream *out = nullptr;

// Write to stdout in absence of an output file.
if (options.outputFile().empty()) {
out = &std::cout;
} else {
out = openFile(options.outputFile(), false);
if (!(*out)) {
::error(ErrorType::ERR_NOT_FOUND, "%2%: No such file or directory.",
options.outputFile().string());
options.usage();
return EXIT_FAILURE;
}
}
std::stringstream formattedOutput;

const IR::P4Program *program = P4::parseP4File(options);

if (program == nullptr && ::errorCount() != 0) {
return EXIT_FAILURE;
::error("Failed to parse P4 file.");
return formattedOutput;
}

auto top4 = P4::ToP4(out, false);

*out << "\n############################## INITIAL ##############################\n";
auto top4 = P4::ToP4(&formattedOutput, false);
// Print the program before running front end passes.
program->apply(top4);

return ::errorCount() > 0 ? EXIT_FAILURE : EXIT_SUCCESS;
if (::errorCount() > 0) {
::error("Failed to format p4 program.");
return formattedOutput;
}

return formattedOutput;
}
10 changes: 10 additions & 0 deletions backends/p4fmt/p4fmt.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#ifndef BACKENDS_P4FMT_P4FMT_H_
#define BACKENDS_P4FMT_P4FMT_H_

#include <filesystem>
#include <sstream>

/// Formats a P4 program from the input file, returns formatted output
std::stringstream getFormattedOutput(std::filesystem::path inputFile);

#endif /* BACKENDS_P4FMT_P4FMT_H_ */
20 changes: 20 additions & 0 deletions backends/p4fmt/ref.p4
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* -*- P4_16 -*- */

const bit<16> TYPE_IPV4 = 0x800;

typedef bit<9> egressSpec_t;
typedef bit<48> macAddr_t;
typedef bit<32> ip4Addr_t;

header ethernet_t {
macAddr_t dstAddr;

// comm1
macAddr_t srcAddr; // comm2

bit<16> etherType;
}

struct headers {
ethernet_t ethernet;
}
182 changes: 182 additions & 0 deletions backends/p4fmt/refcheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// Ref: https://github.com/fruffy/flay/blob/master/tools/reference_checker.cpp

#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <optional>

#include "backends/p4fmt/p4fmt.h"
#include "backends/p4tools/common/lib/logging.h"
#include "options.h"
#include "p4fmt.h"

namespace P4Fmt {

namespace {
class ReferenceCheckerOptions : protected P4fmtOptions {
/// The input file to process
std::optional<std::filesystem::path> inputFile;

/// The reference file to compare against.
std::optional<std::filesystem::path> referenceFile;

/// Overwrite the reference file and do not compare against it.
bool overwriteReferences = false;

public:
ReferenceCheckerOptions() {
registerOption(
"--file", "inputFile",
[this](const char *arg) {
inputFile = arg;
if (!std::filesystem::exists(inputFile.value())) {
::error("The input P4 program '%s' does not exist.", inputFile.value().c_str());
return false;
}
return true;
},
"The input file to process.");
registerOption(
"--overwrite", nullptr,
[this](const char *) {
overwriteReferences = true;
return true;
},
"Do not check references, instead overwrite the reference.");
registerOption(
"--reference-file", "referenceFile",
[this](const char *arg) {
referenceFile = arg;
return true;
},
"The reference file to compare against.");
}
~ReferenceCheckerOptions() override = default;

int processOptions(int argc, char *argv[]) {
auto *unprocessedOptions = P4fmtOptions::process(argc, argv);

if (unprocessedOptions != nullptr && !unprocessedOptions->empty()) {
for (const auto &option : *unprocessedOptions) {
::error("Unprocessed input: %s", option);
}
return EXIT_FAILURE;
}

if (!inputFile.has_value()) {
::error("No input file specified.");
return EXIT_FAILURE;
}
if (!referenceFile.has_value()) {
::error(
"Reference file has not been specified. Use "
"--reference-file.");
return EXIT_FAILURE;
}
// If the environment variable P4FMT_REPLACE is set, overwrite the reference file.
if (std::getenv("P4FMT_REPLACE") != nullptr) {
overwriteReferences = true;
}
return EXIT_SUCCESS;
}

[[nodiscard]] const std::filesystem::path &getInputFile() const { return inputFile.value(); }

[[nodiscard]] std::optional<std::filesystem::path> getReferenceFile() const {
return referenceFile;
}

[[nodiscard]] bool doOverwriteReferences() const { return overwriteReferences; }
};

int compareAgainstReference(const std::stringstream &formattedOutput,
const std::filesystem::path &referenceFile) {
// Construct the command to invoke the diff utility
std::stringstream command;
command << "echo " << std::quoted(formattedOutput.str());
command << "| diff --color -u " << referenceFile.c_str() << " -";

// Open a pipe to capture the output of the diff command.
P4Tools::printInfo("Running diff command: \"%s\"", command.str());
FILE *pipe = popen(command.str().c_str(), "r");
if (pipe == nullptr) {
::error("Unable to create pipe to diff command.");
return EXIT_FAILURE;
}
// Read and print the output of the diff command.
std::stringstream result;
char buffer[128];
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
result << buffer;
}
if (pclose(pipe) != 0) {
::error("Diff command failed.\n%1%", result.str());
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

std::optional<std::filesystem::path> getFilePath(const ReferenceCheckerOptions &options,
const std::filesystem::path &basePath,
std::string_view suffix) {
auto referenceFileOpt = options.getReferenceFile();
auto referencePath = basePath;
if (referenceFileOpt.has_value()) {
// If a reference file is explicitly provided, just overwrite this file.
referencePath = referenceFileOpt.value();
} else {
::error("Reference file has not been specified.");
return std::nullopt;
}
return referencePath.replace_extension(suffix);
}

} // namespace

int run(const ReferenceCheckerOptions &options) {
auto referenceFileOpt = options.getReferenceFile();

std::stringstream formattedOutput = getFormattedOutput(options.getInputFile());

if (formattedOutput.str().empty()) {
::error("Formatting Failed");
return EXIT_FAILURE;
}

if (options.doOverwriteReferences()) {
auto referencePath = getFilePath(options, options.getInputFile().stem(), ".ref");
if (!referencePath.has_value()) {
return EXIT_FAILURE;
}

std::ofstream ofs(referencePath.value());
ofs << formattedOutput << std::endl;
P4Tools::printInfo("Writing reference file %s", referencePath.value().c_str());
ofs.close();
return EXIT_SUCCESS;
}
if (referenceFileOpt.has_value()) {
auto referenceFile = std::filesystem::absolute(referenceFileOpt.value());
return compareAgainstReference(formattedOutput, referenceFile);
}
::error("Reference file has not been specified.");
return EXIT_FAILURE;
}
} // namespace P4Fmt

class RefCheckContext : public BaseCompileContext {};

int main(int argc, char *argv[]) {
AutoCompileContext autoP4RefCheckContext(new RefCheckContext);
P4Fmt::ReferenceCheckerOptions options;

if (options.processOptions(argc, argv) == EXIT_FAILURE || ::errorCount() != 0) {
return EXIT_FAILURE;
}

auto result = P4Fmt::run(options);
if (result == EXIT_FAILURE) {
return EXIT_FAILURE;
}
return ::errorCount() == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
20 changes: 20 additions & 0 deletions backends/p4fmt/sample.p4
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* -*- P4_16 -*- */

const bit<16> TYPE_IPV4 = 0x800;

typedef bit<9> egressSpec_t;
typedef bit<48> macAddr_t;
typedef bit<32> ip4Addr_t;

header ethernet_t {
macAddr_t dstAddr;

// comm1
macAddr_t srcAddr; // comm2

bit<16> etherType;
}

struct headers {
ethernet_t ethernet;
}

0 comments on commit e135f41

Please sign in to comment.