diff --git a/.github/workflows/ci-test-debian.yml b/.github/workflows/ci-test-debian.yml index b401433fac4..baeeb6f6ce7 100644 --- a/.github/workflows/ci-test-debian.yml +++ b/.github/workflows/ci-test-debian.yml @@ -38,8 +38,8 @@ jobs: tools/ci-build.sh - name: Run tests (Ubuntu 22.04) - # Need to use sudo for the eBPF kernel tests. - run: sudo -E ctest --output-on-failure --schedule-random + # Need to use sudo for the eBPF kernel tests and need to avoid p4tc_stf tests. + run: sudo -E ctest --output-on-failure --schedule-random -E "p4tc_samples_stf|p4tc_cleanup|p4tc_setup" working-directory: ./build # Build with GCC and test Tofino backend on Ubuntu 22.04. @@ -116,7 +116,7 @@ jobs: tools/ci-build.sh - name: Run tests (Ubuntu 20.04) - # Need to use sudo for the eBPF kernel tests. - run: sudo -E ctest --output-on-failure --schedule-random + # Need to use sudo for the eBPF kernel tests and need to avoid p4tc_stf tests. + run: sudo -E ctest --output-on-failure --schedule-random -E "p4tc_samples_stf|p4tc_cleanup|p4tc_setup" working-directory: ./build if: matrix.unity == 'ON' && matrix.gtest == 'ON' diff --git a/.github/workflows/ci-test-fedora.yml b/.github/workflows/ci-test-fedora.yml index 2b4a4d95287..3ba995b08f3 100644 --- a/.github/workflows/ci-test-fedora.yml +++ b/.github/workflows/ci-test-fedora.yml @@ -50,5 +50,6 @@ jobs: - name: Run p4c tests (Fedora Linux) run: | - export PATH="$HOME/.local/bin:$PATH"; ctest --output-on-failure --schedule-random + # Avoid running p4tc stf tests for now + export PATH="$HOME/.local/bin:$PATH"; ctest --output-on-failure --schedule-random -E "p4tc_samples_stf|p4tc_cleanup|p4tc_setup" working-directory: ./build diff --git a/.github/workflows/ci-test-mac.yml b/.github/workflows/ci-test-mac.yml index 4c8845dbe07..5cbe13fd110 100644 --- a/.github/workflows/ci-test-mac.yml +++ b/.github/workflows/ci-test-mac.yml @@ -58,7 +58,7 @@ jobs: - name: Run tests (MacOS) run: | source ~/.bash_profile - ctest --output-on-failure --schedule-random -E "bpf|ubpf|testgen|smith" + ctest --output-on-failure --schedule-random -E "bpf|ubpf|testgen|smith|p4tc" working-directory: ./build # Build and test p4c on MacOS 13 on x86. @@ -105,5 +105,5 @@ jobs: - name: Run tests (MacOS) run: | source ~/.bash_profile - ctest --output-on-failure --schedule-random -E "bpf|ubpf|testgen|smith" + ctest --output-on-failure --schedule-random -E "bpf|ubpf|testgen|smith|p4tc" working-directory: ./build diff --git a/.github/workflows/ci-ubuntu-18-nightly-p4tc-stf.yml b/.github/workflows/ci-ubuntu-18-nightly-p4tc-stf.yml new file mode 100644 index 00000000000..9149fba7e47 --- /dev/null +++ b/.github/workflows/ci-ubuntu-18-nightly-p4tc-stf.yml @@ -0,0 +1,43 @@ +name: "test-p4c-ubuntu-18.04" + +on: + schedule: + # Every day on midnight UTC + - cron: "0 0 * * *" + pull_request: + branches: [main] + push: + branches: [main] + +jobs: + # Run p4tc stf tests + test-ubuntu18-p4tc-stf: + # Only run on pull requests with the "p4tc" label. + if: ${{ github.event_name == 'schedule' || contains(github.event.pull_request.labels.*.name, 'p4tc') }} + runs-on: ubuntu-20.04 + env: + CTEST_PARALLEL_LEVEL: 4 + IMAGE_TYPE: test + ENABLE_GTESTS: ${{ matrix.gtest }} + CMAKE_UNITY_BUILD: ${{ matrix.unity }} + BUILD_GENERATOR: Ninja + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + + - name: ccache + uses: hendrikmuhs/ccache-action@v1 + with: + key: test-${{ matrix.unity }}-${{ runner.os }}-gcc + max-size: 1000M + + - name: Build (Ubuntu 20.04, GCC) + run: | + tools/ci-build.sh + + - name: Run tests (Ubuntu 20.04) + # Need to use sudo for the eBPF kernel tests. + run: sudo -E ctest --output-on-failure --schedule-random -R "p4tc_samples_stf|p4tc_cleanup|p4tc_setup" + working-directory: ./build diff --git a/.github/workflows/ci-ubuntu-18-nightly.yml b/.github/workflows/ci-ubuntu-18-nightly.yml index 68ada935503..ad5e5374376 100644 --- a/.github/workflows/ci-ubuntu-18-nightly.yml +++ b/.github/workflows/ci-ubuntu-18-nightly.yml @@ -39,4 +39,5 @@ jobs: # this is needed to create network namespaces for the ebpf tests. - name: Run tests (Ubuntu 18.04) run: | - sudo -E docker run --privileged -w /p4c/build -e $CTEST_PARALLEL_LEVEL p4c ctest --output-on-failure --schedule-random + sudo -E docker run --privileged -w /p4c/build -e $CTEST_PARALLEL_LEVEL p4c ctest --output-on-failure --schedule-random \ + -E "p4tc_samples_stf|p4tc_cleanup|p4tc_setup" diff --git a/.github/workflows/ci-ubuntu-20-sanitizer-nightly.yml b/.github/workflows/ci-ubuntu-20-sanitizer-nightly.yml index 830ddade2cd..c5496e9e6b7 100644 --- a/.github/workflows/ci-ubuntu-20-sanitizer-nightly.yml +++ b/.github/workflows/ci-ubuntu-20-sanitizer-nightly.yml @@ -40,5 +40,5 @@ jobs: - name: Run tests (Ubuntu 20.04) # Need to use sudo for the eBPF kernel tests. - run: sudo -E ctest --output-on-failure --schedule-random + run: sudo -E ctest --output-on-failure --schedule-random -E "p4tc_samples_stf|p4tc_cleanup|p4tc_setup" working-directory: ./build diff --git a/backends/ebpf/codeGen.h b/backends/ebpf/codeGen.h index a359f38ca0e..632b893bc98 100644 --- a/backends/ebpf/codeGen.h +++ b/backends/ebpf/codeGen.h @@ -82,8 +82,8 @@ class CodeGenInspector : public Inspector { } bool isPointerVariable(cstring name) { return asPointerVariables.count(name) > 0; } - bool notSupported(const IR::Expression *expression) { - ::P4::error(ErrorType::ERR_UNSUPPORTED, "%1%: not yet implemented", expression); + bool notSupported(const IR::Node *n) { + ::P4::error(ErrorType::ERR_UNSUPPORTED, "%1%: not yet implemented", n); return false; } @@ -117,6 +117,7 @@ class CodeGenInspector : public Inspector { bool preorder(const IR::Type_Enum *type) override; void emitAssignStatement(const IR::Type *ltype, const IR::Expression *lexpr, cstring lpath, const IR::Expression *rexpr); + bool preorder(const IR::BaseAssignmentStatement *s) override { return notSupported(s); } bool preorder(const IR::AssignmentStatement *s) override; bool preorder(const IR::BlockStatement *s) override; bool preorder(const IR::MethodCallStatement *s) override; diff --git a/backends/ebpf/ebpfParser.h b/backends/ebpf/ebpfParser.h index a88debcf7f3..7ba1e544a9a 100644 --- a/backends/ebpf/ebpfParser.h +++ b/backends/ebpf/ebpfParser.h @@ -62,6 +62,7 @@ class StateTranslationVisitor : public CodeGenInspector { builder->endOfStatement(true); return false; } + bool preorder(const IR::BaseAssignmentStatement *stat) override { return notSupported(stat); } bool preorder(const IR::AssignmentStatement *stat) override; }; diff --git a/backends/ebpf/psa/ebpfPsaControl.h b/backends/ebpf/psa/ebpfPsaControl.h index c6273f7a12a..3ebcf7797cd 100644 --- a/backends/ebpf/psa/ebpfPsaControl.h +++ b/backends/ebpf/psa/ebpfPsaControl.h @@ -31,6 +31,7 @@ class ControlBodyTranslatorPSA : public ControlBodyTranslator { public: explicit ControlBodyTranslatorPSA(const EBPFControlPSA *control); + bool preorder(const IR::BaseAssignmentStatement *a) override { return notSupported(a); } bool preorder(const IR::AssignmentStatement *a) override; void processMethod(const P4::ExternMethod *method) override; diff --git a/backends/graphs/controls.cpp b/backends/graphs/controls.cpp index d4b3907bd65..093c4e130f7 100644 --- a/backends/graphs/controls.cpp +++ b/backends/graphs/controls.cpp @@ -226,7 +226,7 @@ bool ControlGraphs::preorder(const IR::MethodCallStatement *statement) { return false; } -bool ControlGraphs::preorder(const IR::AssignmentStatement *statement) { +bool ControlGraphs::preorder(const IR::BaseAssignmentStatement *statement) { statementsStack.push_back(statement); return false; } diff --git a/backends/graphs/controls.h b/backends/graphs/controls.h index c16fc2f77d4..b25bfc05949 100644 --- a/backends/graphs/controls.h +++ b/backends/graphs/controls.h @@ -45,7 +45,7 @@ class ControlGraphs : public Graphs { bool preorder(const IR::IfStatement *statement) override; bool preorder(const IR::SwitchStatement *statement) override; bool preorder(const IR::MethodCallStatement *statement) override; - bool preorder(const IR::AssignmentStatement *statement) override; + bool preorder(const IR::BaseAssignmentStatement *statement) override; bool preorder(const IR::ReturnStatement *) override; bool preorder(const IR::ExitStatement *) override; bool preorder(const IR::P4Table *table) override; diff --git a/backends/p4fmt/p4formatter.cpp b/backends/p4fmt/p4formatter.cpp index 6229a16201e..f95ba85071d 100644 --- a/backends/p4fmt/p4formatter.cpp +++ b/backends/p4fmt/p4formatter.cpp @@ -920,6 +920,16 @@ bool P4Formatter::preorder(const IR::AssignmentStatement *a) { return false; } +bool P4Formatter::preorder(const IR::OpAssignmentStatement *a) { + visit(a->left); + builder.append(" "); + builder.append(a->getStringOp()); + builder.append("= "); + visit(a->right); + builder.endOfStatement(); + return false; +} + bool P4Formatter::preorder(const IR::BlockStatement *s) { if (printAnnotations(s)) builder.spc(); builder.blockStart(); diff --git a/backends/p4fmt/p4formatter.h b/backends/p4fmt/p4formatter.h index daa31ec3a6c..3df67f9d33c 100644 --- a/backends/p4fmt/p4formatter.h +++ b/backends/p4fmt/p4formatter.h @@ -248,6 +248,7 @@ class P4Formatter : public Inspector, ::P4::ResolutionContext { // statements bool preorder(const IR::AssignmentStatement *s) override; + bool preorder(const IR::OpAssignmentStatement *s) override; bool preorder(const IR::BlockStatement *s) override; bool preorder(const IR::MethodCallStatement *s) override; bool preorder(const IR::EmptyStatement *s) override; diff --git a/backends/p4test/p4test.cpp b/backends/p4test/p4test.cpp index 641830a275e..6b906302b7a 100644 --- a/backends/p4test/p4test.cpp +++ b/backends/p4test/p4test.cpp @@ -87,6 +87,39 @@ class P4TestOptions : public CompilerOptions { } }; +class P4TestPragmas : public P4::P4COptionPragmaParser { + std::optional tryToParse( + const IR::Annotation *annotation) { + if (annotation->name == "test_keep_opassign") { + test_keepOpAssign = true; + return std::nullopt; + } + return P4::P4COptionPragmaParser::tryToParse(annotation); + } + + public: + P4TestPragmas() : P4::P4COptionPragmaParser(true) {} + + bool test_keepOpAssign = false; +}; + +class TestFEPolicy : public P4::FrontEndPolicy { + const P4TestPragmas &pragmas; + + P4::ParseAnnotations *getParseAnnotations() const { + return new P4::ParseAnnotations("p4test", true, + P4::ParseAnnotations::HandlerMap({ + PARSE_EMPTY("test_keep_opassign"_cs), + }), + false); + } + + bool removeOpAssign() const { return !pragmas.test_keepOpAssign; } + + public: + explicit TestFEPolicy(const P4TestPragmas &pragmas) : pragmas(pragmas) {} +}; + using P4TestContext = P4CContextWithOptions; static void log_dump(const IR::Node *node, const char *head) { @@ -135,13 +168,14 @@ int main(int argc, char *const argv[]) { info.emitInfo("PARSER"); if (program != nullptr && ::P4::errorCount() == 0) { - P4::P4COptionPragmaParser optionsPragmaParser(true); - program->apply(P4::ApplyOptionsPragmas(optionsPragmaParser)); + P4TestPragmas testPragmas; + program->apply(P4::ApplyOptionsPragmas(testPragmas)); info.emitInfo("PASS P4COptionPragmaParser"); if (!options.parseOnly) { try { - P4::FrontEnd fe; + TestFEPolicy fe_policy(testPragmas); + P4::FrontEnd fe(&fe_policy); fe.addDebugHook(hook); // use -TdiagnosticCountInPass:1 / -TdiagnosticCountInPass:4 to get output of // this hook diff --git a/backends/p4tools/modules/smith/common/statements.cpp b/backends/p4tools/modules/smith/common/statements.cpp index 775871e5b92..dde08165fd7 100644 --- a/backends/p4tools/modules/smith/common/statements.cpp +++ b/backends/p4tools/modules/smith/common/statements.cpp @@ -166,10 +166,6 @@ void StatementGenerator::removeLval(const IR::Expression *left, const IR::Type * } IR::Statement *StatementGenerator::genAssignmentStatement() { - IR::AssignmentStatement *assignstat = nullptr; - IR::Expression *left = nullptr; - IR::Expression *right = nullptr; - std::vector percent = { Probabilities::get().ASSIGNMENTORMETHODCALLSTATEMENT_ASSIGN_BIT, Probabilities::get().ASSIGNMENTORMETHODCALLSTATEMENT_ASSIGN_STRUCTLIKE}; @@ -183,11 +179,11 @@ IR::Statement *StatementGenerator::genAssignmentStatement() { // TODO(fruffy): Find a more meaningful assignment statement return nullptr; } - left = target().expressionGenerator().pickLvalOrSlice(bitType); + auto *left = target().expressionGenerator().pickLvalOrSlice(bitType); if (P4Scope::constraints.single_stage_actions) { removeLval(left, bitType); } - right = target().expressionGenerator().genExpression(bitType); + auto *right = target().expressionGenerator().genExpression(bitType); return new IR::AssignmentStatement(left, right); } case 1: @@ -195,7 +191,7 @@ IR::Statement *StatementGenerator::genAssignmentStatement() { break; } - return assignstat; + return nullptr; } IR::Statement *StatementGenerator::genMethodCallExpression(const IR::PathExpression *methodName, diff --git a/backends/p4tools/modules/testgen/lib/collect_coverable_nodes.cpp b/backends/p4tools/modules/testgen/lib/collect_coverable_nodes.cpp index 66c9d3dbcc9..64288da10c0 100644 --- a/backends/p4tools/modules/testgen/lib/collect_coverable_nodes.cpp +++ b/backends/p4tools/modules/testgen/lib/collect_coverable_nodes.cpp @@ -48,7 +48,7 @@ bool CoverableNodesScanner::preorder(const IR::ParserState *parserState) { return true; } -bool CoverableNodesScanner::preorder(const IR::AssignmentStatement *stmt) { +bool CoverableNodesScanner::preorder(const IR::BaseAssignmentStatement *stmt) { // Only track statements, which have a valid source position in the P4 program. if (coverageOptions.coverStatements && stmt->getSourceInfo().isValid()) { coverableNodes.insert(stmt); diff --git a/backends/p4tools/modules/testgen/lib/collect_coverable_nodes.h b/backends/p4tools/modules/testgen/lib/collect_coverable_nodes.h index 5d9d3253bd7..059fb69fb59 100644 --- a/backends/p4tools/modules/testgen/lib/collect_coverable_nodes.h +++ b/backends/p4tools/modules/testgen/lib/collect_coverable_nodes.h @@ -34,7 +34,7 @@ class CoverableNodesScanner : public Inspector { /// Statement coverage. bool preorder(const IR::ParserState *parserState) override; - bool preorder(const IR::AssignmentStatement *stmt) override; + bool preorder(const IR::BaseAssignmentStatement *stmt) override; bool preorder(const IR::MethodCallStatement *stmt) override; bool preorder(const IR::ExitStatement *stmt) override; bool preorder(const IR::MethodCallExpression *call) override; diff --git a/backends/tc/CMakeLists.txt b/backends/tc/CMakeLists.txt index 2e3ab1594f5..2889a297e24 100644 --- a/backends/tc/CMakeLists.txt +++ b/backends/tc/CMakeLists.txt @@ -14,6 +14,49 @@ # and limitations under the License. #*****************************************************************************/ +if(NOT APPLE) + # Fetch and declare the libbpf library. Print out download state while setting up libbpf. + set(FETCHCONTENT_QUIET_PREV ${FETCHCONTENT_QUIET}) + set(FETCHCONTENT_QUIET OFF) + fetchcontent_declare( + p4cbpfrepo + URL https://github.com/libbpf/libbpf/archive/refs/tags/v1.5.0.tar.gz + URL_HASH SHA256=53492aff6dd47e4da04ef5e672d753b9743848bdb38e9d90eafbe190b7983c44 + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/runtime/libbpf + USES_TERMINAL_DOWNLOAD TRUE + GIT_PROGRESS TRUE + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + ) + fetchcontent_makeavailable(p4cbpfrepo) + set(FETCHCONTENT_QUIET ${FETCHCONTENT_QUIET_PREV}) + message("Building libbpf...") + execute_process( + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/runtime/build-libbpf + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMAND_ECHO STDOUT + ) + message("Done with setting up libbpf for P4C.") + + fetchcontent_declare( + iproute2repo + URL https://github.com/p4tc-dev/iproute2-p4tc-pub/archive/refs/tags/release-v17-rc6.tar.gz + URL_HASH SHA256=624c32a571f9f30d1070d9b23e96121ac79f9273df9ff6db4ee6d034ab983c5d + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/runtime/iproute2-p4tc-pub + USES_TERMINAL_DOWNLOAD TRUE + GIT_PROGRESS TRUE + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + ) + fetchcontent_makeavailable(iproute2repo) + set(FETCHCONTENT_QUIET ${FETCHCONTENT_QUIET_PREV}) + message("Building iproute2...") + execute_process( + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/runtime/build-iproute2 ${CMAKE_CURRENT_SOURCE_DIR}/runtime + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMAND_ECHO STDOUT + ) + message("Done with setting up iproute2 for P4C.") +endif() + set(P4TC_BACKEND_SOURCES backend.cpp ebpfCodeGen.cpp @@ -123,4 +166,21 @@ set(P4TC_COMPILER_DRIVER "${CMAKE_CURRENT_SOURCE_DIR}/run-tc-test.py") set (P4_16_SUITES "${P4C_SOURCE_DIR}/testdata/p4tc_samples/*.p4") +# Setup fixture +add_test(NAME p4tc_setup COMMAND bash ${P4C_SOURCE_DIR}/backends/tc/runtime/setup "https://api.github.com/repos/p4tc-dev/linux-p4tc-pub/releases/latest") +set_tests_properties(p4tc_setup PROPERTIES FIXTURES_SETUP P4TCFixture) + +add_test(NAME p4tc_cleanup COMMAND bash ${P4C_SOURCE_DIR}/backends/tc/runtime/cleanup) +set_tests_properties(p4tc_cleanup PROPERTIES FIXTURES_CLEANUP P4TCFixture) + +macro(p4tc_add_test_with_args tag driver isXfail alias p4test test_args cmake_args) + p4c_add_test_with_args(${tag} ${driver} ${isXfail} ${alias} ${p4test} ${test_args} "") + p4c_test_set_name(__testname ${tag} ${alias}) + set_tests_properties(${__testname} PROPERTIES FIXTURES_REQUIRED P4TCFixture RESOURCE_LOCK "shared_lock") + set_tests_properties(${__testname} PROPERTIES RESOURCE_LOCK "shared_lock") + set_tests_properties(${__testname} PROPERTIES TIMEOUT 1000) +endmacro(p4tc_add_test_with_args) + p4c_add_tests("p4tc" ${P4TC_COMPILER_DRIVER} "${P4_16_SUITES}" "") +p4tc_add_test_with_args("p4tc" ${P4TC_COMPILER_DRIVER} FALSE "testdata/p4tc_samples_stf/arp_respond.p4" "testdata/p4tc_samples_stf/arp_respond.p4" "-tf ${P4C_SOURCE_DIR}/testdata/p4tc_samples_stf/arp_respond.stf" "") +p4tc_add_test_with_args("p4tc" ${P4TC_COMPILER_DRIVER} FALSE "testdata/p4tc_samples_stf/simple_l3.p4" "testdata/p4tc_samples_stf/simple_l3.p4" "-tf ${P4C_SOURCE_DIR}/testdata/p4tc_samples_stf/simple_l3.stf" "") diff --git a/backends/tc/run-tc-test.py b/backends/tc/run-tc-test.py index 2867799b810..535bf917f14 100755 --- a/backends/tc/run-tc-test.py +++ b/backends/tc/run-tc-test.py @@ -15,8 +15,10 @@ # and limitations under the License. # *******************************************************************************/ +import argparse import difflib import os +import re import shutil import stat import sys @@ -25,225 +27,192 @@ from subprocess import Popen, call from threading import Thread -SUCCESS = 0 -FAILURE = 1 +from test_infra import TCInfra + +FILE_DIR = Path(__file__).resolve().parent +sys.path.append(str(FILE_DIR.joinpath("../../tools"))) +import testutils + +PARSER = argparse.ArgumentParser() +PARSER.add_argument("compiler_src_dir", help="the root directory of the compiler source tree") +PARSER.add_argument("p4filename", help="the p4 file to process") +PARSER.add_argument( + "-c", + "--compiler", + dest="compiler", + default="p4c-pna-p4tc", + help="Specify the path to the compiler binary, default is p4c-pna-p4tc", +) +PARSER.add_argument( + "-cb", + "--clang-bin", + dest="clang", + action='store_true', + default="clang-15", + help="Specify clang binary, default is clang-15", +) +PARSER.add_argument( + "-tf", + "--testfile", + dest="testfile", + help=( + "Provide the path for the stf file for this test. " + "If no path is provided, the script will search for an" + " stf file in the same folder." + ), +) +PARSER.add_argument( + "-v", + "--verbose", + dest="verbose", + action='store_true', + help=("Verbose output"), +) +PARSER.add_argument( + "-b", + dest="cleanupTmp", + action='store_true', + help=("Do not remove temporary results for failing tests"), +) +PARSER.add_argument( + "-f", + "--replace", + dest="replace", + action='store_true', + help=("Replace"), +) class Options(object): def __init__(self): self.binary = "" # this program's name - self.cleanupTmp = False # if false do not remote tmp folder created + self.cleanupTmp = True # if false do not remote tmp folder created self.p4Filename = "" # file that is being compiled - self.compilerSrcDir = "" # path to compiler source tree + self.compiler_src_dir = "" # path to compiler source tree self.verbose = False self.replace = False # replace previous outputs + self.compiler = "" + self.clang = "" + self.p4filename = "" + self.testfile = False + self.testdir = "" + self.runtimedir = str(FILE_DIR.joinpath("runtime")) self.compilerOptions = [] -def usage(options): - name = options.binary - print(name, "usage:") - print(name, "rootdir [options] file.p4") - print("Invokes compiler on the supplied file, possibly adding extra arguments") - print("`rootdir` is the root directory of the compiler source tree") - print("options:") - print(" -b: do not remove temporary results for failing tests") - print(" -v: verbose operation") - print(" -f: replace reference outputs with newly generated ones") - - -def isError(p4filename): - # True if the filename represents a p4 program that should fail - return "_errors" in p4filename - - -class Local(object): - # object to hold local vars accessible to nested functions - pass - - -def run_timeout(options, args, timeout, stderr): - if options.verbose: - print("Executing ", " ".join(args)) - local = Local() - local.process = None - - def target(): - procstderr = None - if stderr is not None: - procstderr = open(stderr, "w") - local.process = Popen(args, stdout=procstderr, stderr=procstderr) - local.process.wait() - - thread = Thread(target=target) - thread.start() - thread.join(timeout) - if thread.is_alive(): - print("Timeout ", " ".join(args), file=sys.stderr) - local.process.terminate() - thread.join() - if local.process is None: - # never even started - if options.verbose: - print("Process failed to start") - return -1 - if options.verbose: - print("Exit code ", local.process.returncode) - return local.process.returncode - - -timeout = 10 * 60 - - -def compare_files(options, produced, expected): - if options.verbose: - print("Comparing ", produced, " and ", expected) - if produced.endswith(".c"): - sedcommand = "sed -i.bak '1d;2d' " + produced - call(sedcommand, shell=True) - if produced.endswith(".h"): - sedcommand = "sed -i.bak '1d;2d' " + produced - call(sedcommand, shell=True) - sedcommand = "sed -i -e 's/[a-zA-Z0-9_\/\-]*testdata\///g' " + produced - call(sedcommand, shell=True) - - if options.replace: - if options.verbose: - print("Saving new version of ", expected) - shutil.copy2(produced, expected) - return SUCCESS - - diff = difflib.Differ().compare(open(produced).readlines(), open(expected).readlines()) - result = SUCCESS - - message = "" - for l in diff: - if l[0] == ' ': - continue - result = FAILURE - message += l - - if message != "": - print("Files ", produced, " and ", expected, " differ:", file=sys.stderr) - print(message, file=sys.stderr) +def import_from(module, name): + """Try to import a module and class directly instead of the typical + Python method. Allows for dynamic imports.""" + module = __import__(module, fromlist=[name]) + return getattr(module, name) + + +def run_model(tc, testfile): + result = tc.compile_p4() + if result != testutils.SUCCESS: + return result + + result = tc.compare_compiled_files() + if result != testutils.SUCCESS: + return result + + # If there is no testfile, just run the compliation testing + if not testfile: + return result + + result = tc.spawn_bridge() + if result != testutils.SUCCESS: + return result + + result = tc.boot() + if result != testutils.SUCCESS: + return result + + result = tc.run(testfile) + if result != testutils.SUCCESS: + return result + + result = tc.check_outputs() + if result != testutils.SUCCESS: + return result return result -def check_generated_files(options, tmpdir, expecteddir): - files = os.listdir(tmpdir) - for file in files: - if options.verbose: - print("Checking", file) - produced = tmpdir + "/" + file - expected = expecteddir + "/" + file - if not os.path.isfile(expected): - if options.verbose: - print("Expected file does not exist; creating", expected) - shutil.copy2(produced, expected) - else: - result = compare_files(options, produced, expected) - if result != SUCCESS: - return result - return SUCCESS - - -def process_file(options, argv): +def run_test(options, argv): + """Define the test environment and compile the p4 target + Optional: Run the generated model""" assert isinstance(options, Options) - tmpdir = tempfile.mkdtemp(dir=Path(".").absolute()) - basename = os.path.basename(options.p4filename) - base, ext = os.path.splitext(basename) - dirname = os.path.dirname(options.p4filename) - expected_dirname = dirname + "_outputs" # expected outputs are here - - if options.verbose: - print("Writing temporary files into ", tmpdir) - outputfolder = tmpdir + "/" - stderr = tmpdir + "/" + basename + "-stderr" - - if not os.path.isfile(options.p4filename): - raise Exception("No such file " + options.p4filename) - args = ["./p4c-pna-p4tc", "-o", outputfolder] - args.extend(argv) - print("input: ", options, args, timeout, stderr) - result = run_timeout(options, args, timeout, stderr) - - if result != SUCCESS: - print("Error compiling") - print(" ".join(open(stderr).readlines())) - # If the compiler crashed fail the test - if 'Compiler Bug' in open(stderr).readlines(): - return FAILURE - - expected_error = isError(options.p4filename) - if expected_error: - # invert result - if result == SUCCESS: - result = FAILURE - else: - result = SUCCESS - - if result == SUCCESS: - result = check_generated_files(options, tmpdir, expected_dirname) - - if options.cleanupTmp: - if options.verbose: - print("Removing", tmpdir) - shutil.rmtree(tmpdir) - return result + template, _ = os.path.splitext(options.p4filename) + tmpdir = tempfile.mkdtemp(dir=Path(FILE_DIR.joinpath("runtime")).absolute()) + outputfolder = tmpdir + tc = TCInfra(outputfolder, options, template) -def isdir(path): - try: - return stat.S_ISDIR(os.stat(path).st_mode) - except OSError: - return False + return run_model(tc, options.testfile) ######################### main +def clang_is_installed(clang): + result = testutils.exec_process(f"{clang} --version") + if result.returncode != testutils.SUCCESS: + testutils.log.error("{options.clang} is not installed") + return False + + version_pattern = r'clang version (\d+)\.\d+\.\d+' + + match = re.search(version_pattern, result.output) + if match: + version = match.group(1) + else: + testutils.log.error("Not able to retrieve {options.clang} version") + return False + + if int(version) < 15: + testutils.log.error("{options.clang} version") + return False + + return True + + def main(argv): + args, argv = PARSER.parse_known_args() options = Options() - print("P4TC testing") - options.binary = argv[0] - if len(argv) <= 2: - usage(options) - sys.exit(FAILURE) - - options.compilerSrcdir = argv[1] - argv = argv[2:] - if not os.path.isdir(options.compilerSrcdir): - print(options.compilerSrcdir + " is not a folder", file=sys.stderr) - usage(options) - sys.exit(FAILURE) - - while argv[0][0] == '-': - if argv[0] == "-b": - options.cleanupTmp = False - elif argv[0] == "-v": - options.verbose = True - elif argv[0] == "-f": - options.replace = True - else: - print("Unknown option ", argv[0], file=sys.stderr) - usage(options) - argv = argv[1:] + options.compiler_src_dir = testutils.check_if_dir(Path(args.compiler_src_dir)) + compiler = "./" + args.compiler + + options.compiler = testutils.check_if_file(Path(compiler)) + + if not clang_is_installed(args.clang): + sys.exit(1) + + options.clang = args.clang + + options.p4filename = testutils.check_if_file(Path(args.p4filename)) + options.replace = args.replace + if args.testfile: + options.testfile = testutils.check_if_file(Path(args.testfile)) + options.testdir = tempfile.mkdtemp(dir=os.path.abspath("./")) + + if args.cleanupTmp: + options.cleanupTmp = False + + if args.verbose: + options.verbose = True + + if args.replace: + options.replace = True + + argv = argv[1:] if "P4TEST_REPLACE" in os.environ: options.replace = True - options.p4filename = argv[-1] - options.testName = None - if options.p4filename.startswith(options.compilerSrcdir): - options.testName = options.p4filename[len(options.compilerSrcdir) :] - if options.testName.startswith('/'): - options.testName = options.testName[1:] - if options.testName.endswith('.p4'): - options.testName = options.testName[:-3] + result = run_test(options, argv) - result = process_file(options, argv) sys.exit(result) diff --git a/backends/tc/runtime/build-iproute2 b/backends/tc/runtime/build-iproute2 new file mode 100755 index 00000000000..eb37ebad298 --- /dev/null +++ b/backends/tc/runtime/build-iproute2 @@ -0,0 +1,7 @@ +#!/bin/bash -x + +RUNTIME_DIR=$1 + +cd $RUNTIME_DIR/iproute2-p4tc-pub +./configure --libbpf_dir $RUNTIME_DIR/libbpf/src/root/ +make -j`nproc` diff --git a/backends/tc/runtime/build-libbpf b/backends/tc/runtime/build-libbpf new file mode 100755 index 00000000000..67de40263c9 --- /dev/null +++ b/backends/tc/runtime/build-libbpf @@ -0,0 +1,25 @@ +#!/bin/bash -x +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +P4C_DIR=${SCRIPT_DIR}/../../../ + +pushd $SCRIPT_DIR/libbpf/src +mkdir build root +BUILD_STATIC_ONLY=y OBJDIR=build DESTDIR=root make install +popd +mkdir -p ${SCRIPT_DIR}/include/bpf/ +cp ${SCRIPT_DIR}/libbpf/src/*.h ${SCRIPT_DIR}/include/bpf/ +cp ${P4C_DIR}/backends/ebpf/runtime/*.h ${SCRIPT_DIR}/include/bpf/ diff --git a/backends/tc/runtime/build-simple-p4 b/backends/tc/runtime/build-simple-p4 new file mode 100755 index 00000000000..86206e62d2e --- /dev/null +++ b/backends/tc/runtime/build-simple-p4 @@ -0,0 +1,44 @@ +#!/bin/bash +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +NS_NAME=$1 +PORT0=$2 +PORT0_PAIR=$3 +PORT1=$4 +PORT1_PAIR=$5 +BLOCK_NUM=$6 + +sudo ip netns add $NS_NAME +sudo ip link add $PORT0 address 10:00:00:01:AA:BB type veth peer $PORT0_PAIR address 10:00:00:02:AA:BB +sudo ip link add $PORT1 address 10:00:01:01:AA:BB type veth peer $PORT1_PAIR address 10:00:01:02:AA:BB +sudo ip link set dev $PORT0_PAIR netns $NS_NAME +sudo ip link set dev $PORT1_PAIR netns $NS_NAME +sudo ip a add 10.0.0.1/24 dev $PORT0 +sudo ip a add 2001:db8::1/64 dev $PORT0 +sudo ip neigh add 10.0.0.2 dev $PORT0 lladdr 10:00:00:02:aa:bb +sudo ip netns exec $NS_NAME ip a add 2001:db8::2/64 dev $PORT0_PAIR +sudo ip neigh add 2001:db8::2 dev $PORT0 lladdr 10:00:00:02:aa:bb +sudo ip netns exec $NS_NAME ip a add 10.0.0.2/24 dev $PORT0_PAIR +sudo ip netns exec $NS_NAME ip neigh add 10.0.0.1 dev $PORT0_PAIR lladdr 10:00:00:01:aa:bb +sudo ip netns exec $NS_NAME ip neigh add 2001:db8::1 dev $PORT0_PAIR lladdr 10:00:00:01:aa:bb +sudo ip a add 10.0.1.1/24 dev $PORT1 +sudo ip netns exec $NS_NAME ip a add 10.0.1.2/24 dev $PORT1_PAIR +sudo ip netns exec $NS_NAME ip l set dev $PORT0_PAIR up +sudo ip l set dev $PORT0 up +sudo ip l set dev $PORT1 up +sudo ip netns exec $NS_NAME ip l set dev $PORT1_PAIR up +sudo ip netns exec $NS_NAME tc qdisc add dev $PORT0_PAIR ingress_block $BLOCK_NUM clsact +sudo sysctl -w net.ipv6.conf.$PORT0.router_solicitations=0 +sudo sysctl -w net.ipv6.conf.$PORT1.router_solicitations=0 +sudo ip netns exec $NS_NAME sysctl -w net.ipv6.conf.$PORT0_PAIR.router_solicitations=0 +sudo ip netns exec $NS_NAME sysctl -w net.ipv6.conf.$PORT1_PAIR.router_solicitations=0 diff --git a/backends/tc/runtime/cleanup b/backends/tc/runtime/cleanup new file mode 100755 index 00000000000..65a60cef26a --- /dev/null +++ b/backends/tc/runtime/cleanup @@ -0,0 +1,29 @@ +#!/bin/bash +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# NOTE: THIS SCRIPT SHOULD NOT BE EXECUTED MANUALLY + +RUNTIME_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +EXTRACT_DIR="$RUNTIME_DIR/extracted_deb" + +if ! [ -f "$RUNTIME_DIR/setup" ]; then + echo "setup and cleanup script should be in the same directory" + exit 1 +fi + +rm -rf $EXTRACT_DIR +rm -f $RUNTIME_DIR/id_rsa +rm -f $RUNTIME_DIR/id_rsa.pub +rm -f linux-image-*.deb + +deluser ci --remove-home diff --git a/backends/tc/runtime/del-bridge b/backends/tc/runtime/del-bridge new file mode 100755 index 00000000000..a644b9c20cd --- /dev/null +++ b/backends/tc/runtime/del-bridge @@ -0,0 +1,16 @@ +#!/bin/bash +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ip l del dev taptc +ip l set dev br1 down +brctl delbr br1 diff --git a/backends/tc/runtime/extract_deb b/backends/tc/runtime/extract_deb new file mode 100755 index 00000000000..98a17a75697 --- /dev/null +++ b/backends/tc/runtime/extract_deb @@ -0,0 +1,22 @@ +#!/bin/bash +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +RELEASE_URL=$1 +EXTRACT_DIR="$SCRIPT_DIR/extracted_deb" + +IMAGE_URL=$(curl -s ${RELEASE_URL} | jq .assets[2].browser_download_url | tr -d \") +echo $IMAGE_URL +wget $IMAGE_URL +dpkg -X linux-image-*.deb ${EXTRACT_DIR} diff --git a/backends/tc/runtime/crc32.h b/backends/tc/runtime/include/crc32.h similarity index 100% rename from backends/tc/runtime/crc32.h rename to backends/tc/runtime/include/crc32.h diff --git a/backends/tc/runtime/pna.h b/backends/tc/runtime/include/pna.h similarity index 100% rename from backends/tc/runtime/pna.h rename to backends/tc/runtime/include/pna.h diff --git a/backends/tc/runtime/send_packet b/backends/tc/runtime/send_packet new file mode 100755 index 00000000000..0fa171c4d0f --- /dev/null +++ b/backends/tc/runtime/send_packet @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +from scapy.all import sendp, rdpcap + +PARSER = argparse.ArgumentParser() +PARSER.add_argument("pcap_file", help="PCAP file to send") +PARSER.add_argument("iface", help="Interface to send the pcap to") + + +def main(): + args, _ = PARSER.parse_known_args() + + pcap_file = args.pcap_file + iface = args.iface + + packets = rdpcap(pcap_file) + print("pcap_file", pcap_file) + print("iface", iface) + print("packets", packets) + for packet in packets: + print("packet", packet) + sendp(packet, iface=iface) + + +if __name__ == "__main__": + main() diff --git a/backends/tc/runtime/setup b/backends/tc/runtime/setup new file mode 100755 index 00000000000..d457b6d3d70 --- /dev/null +++ b/backends/tc/runtime/setup @@ -0,0 +1,28 @@ +#!/bin/bash +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# NOTE: THIS SCRIPT SHOULD NOT BE EXECUTED MANUALLY + +RUNTIME_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +EXTRACT_URL=$1 + +if ! [ -f "$RUNTIME_DIR/cleanup" ]; then + echo "setup and cleanup script should be in the same directory" + exit 1 +fi + +$RUNTIME_DIR/extract_deb $EXTRACT_URL + +sudo useradd -s /bin/bash -m ci + +yes y | ssh-keygen -q -f $RUNTIME_DIR/id_rsa -N '' diff --git a/backends/tc/runtime/spawn-bridge b/backends/tc/runtime/spawn-bridge new file mode 100755 index 00000000000..b98b543b040 --- /dev/null +++ b/backends/tc/runtime/spawn-bridge @@ -0,0 +1,12 @@ +#!/bin/bash + +BRIDGE_IP=$1 + +brctl addbr br1 +ip link set dev br1 up + +ip tuntap add dev taptc mode tap +brctl addif br1 taptc +ip link set dev taptc up + +ip a add $BRIDGE_IP/24 dev br1 diff --git a/backends/tc/runtime/spawn-vm b/backends/tc/runtime/spawn-vm new file mode 100755 index 00000000000..d8f59cf5e15 --- /dev/null +++ b/backends/tc/runtime/spawn-vm @@ -0,0 +1,96 @@ +#!/bin/bash +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +help() { + cat <] [-f config] [-s script] [-n vm_ip] + +Recompile the current kernel, turning on all tc related options in the current .config, +and run the provided command. The original .config file is always preserved. +If no command is provided, "$DEFAULT_CMD" is run inside the VM. +Options: + -h Display this message. + -i Precompiled bzImage to use. + -f Kernel configuration file to use. + Defaults to "$KCONFIG". + -s Script to execute after booting + -n VM ip +EOF +} + +VMCPUS=$(nproc) + +while getopts 'hi:f:s:n:m:u:' OPT; do + case "$OPT" in + h) + help + exit 0 + ;; + m) + MNTDIR="$OPTARG" + if ! [ -d "$MNTDIR" ]; then + echo "$MNTDIR doesn't exist" + exit 1 + fi + ;; + i) + KIMG="$OPTARG" + if ! [ -f "$KIMG" ]; then + echo "$KIMG doesn't exist" + exit 1 + fi + ;; + s) + SCRIPT="$OPTARG" + if ! [ -f "$MNTDIR/$SCRIPT" ]; then + echo "$SCRIPT doesn't exist" + exit 1 + fi + ;; + f) + KCONFIG=$(realpath "$OPTARG") + if ! [ -f "$KCONFIG" ]; then + echo "Configuration file $KCONFIG doesn't exist" + exit 1 + fi + ;; + u) + USER="$OPTARG" + ;; + n) + VM_IP="$OPTARG" + ;; + \? ) + help + exit 1 + ;; + esac +done +shift $((OPTIND -1)) + +source ${MNTDIR}/virtme-ng/bin/activate + +virtme-run \ + --arch "x86_64" \ + --root / \ + --user $USER \ + --kimg "$KIMG" \ + --cpus "$VMCPUS" \ + --memory "512M" \ + --rwdir="/mnt=$MNTDIR" \ + --rwdir="/home/$USER" \ + --show-boot-console \ + --script-sh "/mnt/$SCRIPT $VM_IP" \ + --qemu-opts -nic tap,ifname=taptc,script=no,downscript=no + +deactivate diff --git a/backends/tc/runtime/tc.mk b/backends/tc/runtime/tc.mk new file mode 100644 index 00000000000..841c1381378 --- /dev/null +++ b/backends/tc/runtime/tc.mk @@ -0,0 +1,51 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Actual location of the makefile +ROOT_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) + +## Argument for the CLANG compiler +CLANG ?= clang +override INCLUDES+= -I$(ROOT_DIR)/include -I$(ROOT_DIR)/../../ebpf/runtime/ +override LIBS+= +P4C ?= p4c-pna-p4tc +# Optimization flags to save space +CFLAGS+= -O2 -g -c -D__KERNEL__ -D__ASM_SYSREG_H -DBTF \ + -Wno-unused-value -Wno-pointer-sign \ + -Wno-compare-distinct-pointer-types \ + -Wno-gnu-variable-sized-type-not-at-end \ + -Wno-address-of-packed-member -Wno-tautological-compare \ + -Wno-unknown-warning-option -Wnoparentheses-equality + +TMPL+=$(OUTPUT_DIR)/$(TEMPLATE).template +TMPL+=$(OUTPUT_DIR)/$(TEMPLATE).json +SRCS+=$(OUTPUT_DIR)/$(TEMPLATE)_parser.c +SRCS+=$(OUTPUT_DIR)/$(TEMPLATE)_control_blocks.c +OBJS=$(SRCS:.c=.o) + +all: $(SRCS) $(OBJS) + +$(SRCS): $(P4_FILE) + @if ! ($(P4C) --version); then \ + echo "*** ERROR: Cannot find p4c-ebpf"; \ + exit 1;\ + fi; + $(P4C) $(P4_FILE) -o ${OUTPUT_DIR} + +$(OBJS): %.o : %.c + $(CLANG) $(CFLAGS) $(INCLUDES) --target=bpf -mcpu=probe -c $< -o $@ + +clean: + rm -f $(OBJS) + +realclean: + rm -f $(OBJS) $(SRCS) $(TMPL) $(TEMPLATE)_parser.h diff --git a/backends/tc/runtime/vm-post-boot b/backends/tc/runtime/vm-post-boot new file mode 100755 index 00000000000..db8d9512303 --- /dev/null +++ b/backends/tc/runtime/vm-post-boot @@ -0,0 +1,27 @@ +#!/bin/bash +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +HOST_IP=$1 + +mkdir -p /home/ci/.ssh +sudo bash -c "cat /mnt/id_rsa.pub > /home/ci/.ssh/authorized_keys" + +sudo ip l set dev ens2 up +sudo ip a add $HOST_IP/24 dev ens2 + +sudo mkdir /var/run/sshd +sudo chmod 0755 /var/run/sshd +sudo rm -f /run/nologin +sudo /usr/sbin/sshd -h /mnt/id_rsa + +sleep infinity diff --git a/backends/tc/tcenv.py b/backends/tc/tcenv.py new file mode 100644 index 00000000000..ee00badb785 --- /dev/null +++ b/backends/tc/tcenv.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python3 +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" Virtual environment which models a simple bridge with n attached + interfaces. The bridge runs in a completely isolated namespace. + Allows the loading and testing of eBPF programs. """ + +import logging +import os +import re +import socket +import subprocess +import sys +import time +from pathlib import Path +from typing import Any, List, Optional + +# Append tools to the import path. +FILE_DIR = Path(__file__).resolve().parent +# Append tools to the import path. +sys.path.append(str(FILE_DIR.joinpath("../.."))) +from tools import testutils # pylint: disable=wrong-import-position + + +class NS: + def __init__(self, namespace: str, runtime_dir: str, virtme): + # Identifier of the namespace. + self.ns_name: str = namespace + # List of the veth pair bridge ports. + self.port0 = 'p4port0' + self.port0_pair = 'port0' + self.port1 = 'p4port1' + self.port1_pair = 'port1' + self.block_num = '21' + self.runtime_dir = runtime_dir + self.mnt_dir = '/mnt' + self.port_mapping = (self.port0_pair, self.port1_pair) + self.virtme = virtme + + def ns_init_cmd(self) -> int: + """Initialize the namespace.""" + return f"{self.mnt_dir}/build-simple-p4 {self.ns_name} {self.port0} {self.port0_pair} {self.port1} {self.port1_pair} {self.block_num}" + + def ns_del_cmd(self) -> int: + """Initialize the namespace.""" + + return f"ip netns del {self.ns_name}" + + def ns_init(self) -> int: + """Initialize the namespace.""" + cmd = f"{self.mnt_dir}/build-simple-p4 {self.ns_name} {self.port0} {self.port0_pair} {self.port1} {self.port1_pair} {self.block_num}" + result = testutils.exec_process(cmd) + if result.returncode != testutils.SUCCESS: + testutils.log.error("Failed to create namespace %s", self.ns_name) + + return result.returncode + + def ns_del(self) -> int: + """Delete the namespace and with it all the process running in it.""" + cmd = f"ip netns del {self.ns_name}" + result = testutils.exec_process(cmd, shell=True) + if result.returncode != testutils.SUCCESS: + testutils.log.error("Failed to delete namespace %s", self.ns_name) + return result.returncode + + def get_ns_prefix(self) -> str: + """Return the command prefix for the namespace of this bridge class.""" + return f'ip netns exec {self.ns_name}' + + def get_intros_prefix(self) -> str: + """Return the command prefix for the namespace of this bridge class.""" + return "EXPORT INTROSPECTION=.;" + + def _ns_exec(self, cmd: str, **extra_args: Any): + # bash -c allows us to run multiple commands at once + result = testutils.exec_process(cmd, **extra_args) + if result.returncode != testutils.SUCCESS: + testutils.log.error("Failed to run command in namespace %s", self.ns_name) + return result.returncode + + def ns_exec(self, cmd_string: str, **extra_args: Any) -> int: + """Run and execute an isolated command in the namespace.""" + cmd = self.get_ns_prefix() + " " + cmd_string + return self._ns_exec(cmd, **extra_args) + + def ns_exec_intros(self, cmd_string: str, **extra_args: Any) -> int: + """Run and execute an isolated command in the namespace.""" + cmd = f"{self.get_ns_prefix()} {self.get_intros_prefix()} {cmd_string}" + return self._ns_exec(cmd, **extra_args) + + def ns_proc_open(self) -> Optional[subprocess.Popen]: + """Open a bash process in the namespace and return the handle""" + cmd = self.get_ns_prefix() + " bash" + return testutils.open_process(cmd) + + +class Virtme: + def __init__( + self, + namespace, + runtime_dir, + iproute2_dir, + spawn_script, + post_boot_script, + tmpdir_basename, + verbose, + ): + self.ns = NS(namespace, runtime_dir, self) + self.runtime_dir = runtime_dir + self.iproute2_dir = f"{self.ns.mnt_dir}/{iproute2_dir}" + self.vm_proc = None + self.extract_dir = f"{runtime_dir}/extracted_deb" + self.post_boot_script = post_boot_script + self.spawn_script = spawn_script + self.host_ip = "10.10.11.7" + self.tmpdir_basename = tmpdir_basename + self.release_url = 'https://api.github.com/repos/p4tc-dev/linux-p4tc-pub/releases/latest' + self.port_mapping = (self.ns.port0, self.ns.port1) + self.verbose = verbose + self.user = "ci" + + def __del__(self): + if self.vm_proc: + testutils.kill_proc_group(self.vm_proc) + + def _get_version(self, url): + pattern = r"(\d+\.\d+\.0p\d+tc-v\d+-rc\d+)" + + match = re.search(pattern, url) + + if match: + version = match.group(1) + if self.verbose: + print(f"Extracted version: {version}") + else: + if self.verbose: + print("Version string not found") + + return version + "+" + + def extract_version(self): + cmd = f'curl -s {self.release_url} | jq .assets[2].browser_download_url | tr -d \\"' + result = testutils.exec_process(cmd, shell=True) + if result.returncode != testutils.SUCCESS: + testutils.log.error("Failed to download deb package") + return None + + image_url = result.output + + return self._get_version(image_url) + + def wait_for_ssh(self): + while True: + try: + with socket.create_connection((self.host_ip, 22), timeout=1): + if self.verbose: + print("Connection established") + return + except (socket.timeout, ConnectionRefusedError, OSError): + if self.verbose: + print("Connection still refused") + time.sleep(5) + + def boot(self): + version = self.extract_version() + config_str = f"{self.extract_dir}/boot/config-{version}" + img_str = f"{self.extract_dir}/boot/vmlinuz-{version}" + + cmd = f"{self.spawn_script} -f {config_str} -i {img_str} -m {self.runtime_dir} -s {self.post_boot_script} -n {self.host_ip} -u {self.user}" + self.vm_proc = testutils.open_process(cmd) + if self.vm_proc is None: + testutils.log.error("Failed to boot vm") + return testutils.FAILURE + + # self.monitor_process(self.vm_proc) + self.wait_for_ssh() + + return testutils.SUCCESS + + def get_virtme_prefix(self): + return f'ssh {self.user}@{self.host_ip} -q -i {self.runtime_dir}/id_rsa -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null sudo' + + def ns_init(self): + cmd = f'{self.get_virtme_prefix()} {self.ns.ns_init_cmd()}' + result = testutils.exec_process(cmd) + return result.returncode + + def ns_del(self): + cmd = f'{self.get_virtme_prefix()} {self.ns.ns_del_cmd()}' + return testutils.exec_process(cmd) + + def run_intros(self, cmd): + intros_dir = f"{self.ns.mnt_dir}/{self.tmpdir_basename}" + tc_path = f"{self.iproute2_dir}/tc/tc" + cmd = cmd.format(tc=tc_path, introspection=intros_dir) + cmd = f'{self.get_virtme_prefix()} {self.ns.get_ns_prefix()} /bin/bash -c \"export INTROSPECTION={intros_dir}; export TC={tc_path}; {cmd}\"' + result = testutils.exec_process(cmd) + if result.returncode != testutils.SUCCESS: + testutils.log.error("Failed to run command with introspection") + + return result.returncode + + def run(self, cmd): + result = testutils.exec_process(f"{self.get_virtme_prefix()} {cmd}") + return result.returncode + + def run_script(self, script_name, args): + cmd = f'sudo {self.ns.mnt_dir}/{script_name} ' + " ".join(args) + return self.run(cmd) + + def run_tcpdump(self, filename, port): + remote_pid = -1 + # Define the SSH command + tcpdump_cmd = f"tcpdump -w {filename} -i {port}" + ssh_command = [ + "ssh", + f"{self.user}@{self.host_ip}", + "-i", + f"{self.runtime_dir}/id_rsa", + "-q", + "-o", + "StrictHostKeyChecking=no", + "-o", + "UserKnownHostsFile=/dev/null", + f"sudo nohup bash -c '{tcpdump_cmd} > /dev/null 2>&1 &' && sleep 1 && pgrep -n -f '{tcpdump_cmd}'", + ] + + # Run the command using subprocess to capture the output + process = subprocess.Popen(ssh_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + # Read the output (the PID) + stdout, stderr = process.communicate() + + # Check if we got a PID back + if process.returncode == 0: + remote_pid = stdout.strip().decode() + + return remote_pid + + +def main() -> None: + """main""" + # This is a simple test script which creates/deletes a virtual environment + + # Configure logging. + logging.basicConfig( + filename="ebpfenv.log", + format="%(levelname)s:%(message)s", + level=logging.INFO, + filemode="w", + ) + stderr_log = logging.StreamHandler() + stderr_log.setFormatter(logging.Formatter("%(levelname)s:%(message)s")) + logging.getLogger().addHandler(stderr_log) + + +if __name__ == "__main__": + main() diff --git a/backends/tc/test_infra.py b/backends/tc/test_infra.py new file mode 100644 index 00000000000..ad35683dd7a --- /dev/null +++ b/backends/tc/test_infra.py @@ -0,0 +1,545 @@ +#!/usr/bin/env python3 +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" Represents the call which contains all methods needed to execute a TC + test. Currently five phases are defined: + 1. Invokes the specified compiler on a provided p4 file. + 2. Parses an stf file and generates a pcap output. + 3. Loads the generated template nd the eBPF binaries + 4. Feeds the generated pcap test packets into scapy + 5. Evaluates the output with the expected result from the .stf file +""" + +import difflib +import json +import os +import re +import shutil +import stat +import subprocess +import sys +import time +from glob import glob +from pathlib import Path + +from tcenv import Virtme + +# path to the tools folder of the compiler +# Append tools to the import path. +FILE_DIR = Path(__file__).resolve().parent +# Append tools to the import path. +sys.path.append(str(FILE_DIR.joinpath("../../tools"))) +sys.path.append(str(FILE_DIR.joinpath("../ebpf/targets"))) + +import scapy.utils as scapy_util +import testutils +from scapy.all import sendp +from stf.stf_parser import STFParser + +PCAP_PREFIX = "pcap" # match pattern +PCAP_SUFFIX = ".pcap" # could also be ".pcapng" + + +class P4TCCommand(object): + def __init__(self, pipe_name, cmd_type, table, action, priority="", match=[], extra=""): + # Dir in which all files are stored. + self.pipe_name = pipe_name + # Dir in which all files are stored. + self.cmd_type = cmd_type + # Contains meta information. + self.table = table + # Contains meta information. + self.action = action + # Template to generate a filter. + self.priority = priority + # Contains standard and error output. + self.match = match + # Could also be "pcapng". + self.extra = extra + + +def parse_stf_file(raw_stf, pipe_name): + parser = STFParser() + stf_str = raw_stf.read() + stf_map, errs = parser.parse(stf_str) + input_pkts = {} + cmds = [] + expected = {} + for stf_entry in stf_map: + if stf_entry[0] == "packet": + interface = int(stf_entry[1]) + data = stf_entry[2] + input_pkts.setdefault(interface, []).append(bytes.fromhex("".join(data.split()))) + expected.setdefault(interface, {}) + if data != "": + expected[interface]["any"] = False + expected[interface].setdefault("pkts", []).append(data) + else: + expected[interface]["any"] = True + elif stf_entry[0] == "expect": + interface = int(stf_entry[1]) + pkt_data = stf_entry[2] + expected.setdefault(interface, {}) + if pkt_data != "": + expected[interface]["any"] = False + expected[interface].setdefault("pkts", []).append(pkt_data) + else: + expected[interface]["any"] = True + elif stf_entry[0] == "add": + cmd = P4TCCommand( + pipe_name=pipe_name, + cmd_type=stf_entry[0], + table=stf_entry[1], + priority=stf_entry[2], + match=stf_entry[3], + action=stf_entry[4], + extra=stf_entry[5], + ) + cmds.append(cmd) + return input_pkts, cmds, expected + + +def parse_p4_json(json_file): + data = None + with open(json_file, 'r') as file: + data = json.load(file) + return data + + +def find_action_json(json_table, action_name): + for action in json_table['actions']: + if action['name'] == action_name: + return action + + +def int_to_mac(int_val): + hex_str = '{:012x}'.format(int_val) + mac = ":".join(hex_str[i : i + 2] for i in range(0, 12, 2)) + return mac + + +def map_value(value, port_mapping, json_type): + if json_type == 'dev': + return port_mapping[int(value, 16)] + + if json_type == 'macaddr': + return int_to_mac(int(value, 16)) + + return value + + +def build_runtime_file(rules_file, json_file, cmds, port_mapping): + json_data = parse_p4_json(json_file) + for index, cmd in enumerate(cmds): + generated = "$TC p4ctrl create " + table_name = cmd.table + table_name = table_name.replace(".", "/") + generated += f"{cmd.pipe_name}/table/{table_name} " + json_tables = json_data['tables'] + for table in json_tables: + if table_name == table['name']: + json_table = table + + if not json_table: + return testutils.FAILURE + + if cmd.cmd_type == "add": + for i, key_field_val in enumerate(cmd.match): + key_json = json_table['keyfields'][i] + field = key_field_val[0] + # Support for LPM key + generated += f"{field} " + if isinstance(key_field_val[1], tuple): + key_field_val = map_value(key_field_val[1][0], port_mapping, key_json['type']) + generated += f"{key_field_val[1][0]}/{key_field_val[1][1]} " + else: + key_field_val = map_value(key_field_val[1], port_mapping, key_json['type']) + generated += f"{key_field_val} " + if cmd.action[0] != "_NoAction": + action_name = cmd.action[0].split(".")[1] + action_json = find_action_json(json_table, cmd.action[0].replace(".", "/")) + generated += f"action {action_name} " + for i, val_field in enumerate(cmd.action[1]): + param_json = action_json['params'][i] + param_val = map_value(val_field[1], port_mapping, param_json['type']) + generated += f"param {val_field[0]} {param_val} " + file = open(rules_file, "w") + file.write(generated) + st = os.stat(rules_file) + os.chmod(rules_file, st.st_mode | stat.S_IEXEC) + + return testutils.SUCCESS + + +class Bridge: + def __init__(self, runtimedir, bridge_ip): + self.spawn_bridge_script = f"{runtimedir}/spawn-bridge" + self.del_bridge_script = f"{runtimedir}/del-bridge" + self.bridge_ip = bridge_ip + self.is_spawned = False + + def del_bridge(self): + result = testutils.exec_process(self.del_bridge_script) + if result.returncode != testutils.SUCCESS: + testutils.log.error("Failed to delete bridge") + + self.is_spawned = True + + return result + + def __del__(self): + if self.is_spawned: + self.del_bridge() + + def spawn_bridge(self): + self.is_spawned = True + result = testutils.exec_process(f"{self.spawn_bridge_script} {self.bridge_ip}") + if result.returncode != testutils.SUCCESS: + testutils.log.error("Failed to spawn bridge") + + return result.returncode + + +def isError(p4filename): + # True if the filename represents a p4 program that should fail + return "_errors" in str(p4filename) + + +class TCInfra: + def __init__(self, tmpdir, options, template): + # Dir in which all files are stored. + self.tmpdir = tmpdir + # Contains meta information. + self.options = options + # Template to generate a filter. + self.template = template + # Expected packets per interface. + self.expected = {} + # Location of the runtime folder. + self.runtimedir = options.runtimedir + # Location of the p4c compiler binary. + self.compiler = self.options.compiler + self.spawn_script = f"{self.runtimedir}/spawn-vm" + self.post_boot_script = "vm-post-boot" + self.send_packet_script = "send_packet" + self.bridge_ip = "10.10.11.1" + tmpdir_basename = os.path.basename(tmpdir) + + self.virtme = Virtme( + "p4node", + self.runtimedir, + "iproute2-p4tc-pub", + self.spawn_script, + self.post_boot_script, + tmpdir_basename, + options.verbose, + ) + + self.base_template = os.path.basename(self.template) + # self.outputdir = f"{self.runtimedir}/generated" + self.outputdir = tmpdir + self.bridge = Bridge(options.runtimedir, self.bridge_ip) + self.rules_file = '' + + def __del__(self): + if self.options.cleanupTmp: + shutil.rmtree(self.outputdir) + + def filename(self, prefix, interface, direction): + """Constructs the pcap filename from the given interface and + packet stream direction. For example "pcap1_out.pcap" implies + that the given stream contains tx packets from interface 1""" + return prefix + "/" + PCAP_PREFIX + str(interface) + "_" + direction + PCAP_SUFFIX + + def interface_of_filename(self, f): + """Extracts the interface name out of a pcap filename""" + return int(os.path.basename(f).rstrip(PCAP_SUFFIX).lstrip(PCAP_PREFIX).rsplit("_", 1)[0]) + + def _send_pcap_files(self, iface_pkts_map): + """Writes the collected packets to their respective interfaces. + This is done by creating a pcap file with the corresponding name.""" + result = testutils.SUCCESS + for iface, pkts in iface_pkts_map.items(): + direction = "in" + infile = self.filename(self.runtimedir, iface, direction) + # Linktype 1 the Ethernet Link Type, see also 'man pcap-linktype' + fp = scapy_util.RawPcapWriter(infile, linktype=1) + fp._write_header(None) + for pkt_data in pkts: + try: + fp._write_packet(pkt_data) + except ValueError: + testutils.log.error(f"Invalid packet data {pkt_data}") + return testutils.ProcessResult("", testutils.FAILURE) + fp.flush() + fp.close() + + args = [infile, self.virtme.port_mapping[iface]] + + result = self.virtme.run_script(self.send_packet_script, args) + if result != testutils.SUCCESS: + return result + + return result + + def compile_p4(self): + # Use clang to compile the generated C code to a LLVM IR + args = "make -f tc.mk " + # target makefile + # Source folder of the makefile + args += f"-C {self.runtimedir} " + args += f"TEMPLATE={self.base_template} " + args += f"P4_FILE={self.options.p4filename} " + args += f"OUTPUT_DIR={self.outputdir} " + args += f"P4C={self.compiler} CLANG={self.options.clang}" + # add the folder local to the P4 file to the list of includes + args += f" INCLUDES+=-I{os.path.dirname(self.options.p4filename)}" + result = testutils.exec_process(args) + if result.returncode != testutils.SUCCESS: + testutils.log.error("Failed to compile P4 program") + + returncode = result.returncode + + expected_error = isError(self.options.p4filename) + if expected_error: + if result.returncode == testutils.SUCCESS: + returncode = testutils.FAILURE + else: + returncode = testutils.SUCCESS + + return returncode + + def __compare_compiled_files(self, produced, expected): + if self.options.verbose: + print("Comparing ", produced, " and ", expected) + if produced.endswith(".c"): + sedcommand = "sed -i.bak '1d;2d' " + produced + subprocess.call(sedcommand, shell=True) + if produced.endswith(".h"): + sedcommand = "sed -i.bak '1d;2d' " + produced + subprocess.call(sedcommand, shell=True) + sedcommand = "sed -i -e 's/[a-zA-Z0-9_\/\-]*testdata\///g' " + produced + subprocess.call(sedcommand, shell=True) + + result = testutils.SUCCESS + if self.options.replace: + if self.options.verbose: + print("Saving new version of ", expected) + shutil.copy2(produced, expected) + return result + + diff = difflib.Differ().compare(open(produced).readlines(), open(expected).readlines()) + + message = "" + for l in diff: + if l[0] == ' ': + continue + result = testutils.FAILURE + message += l + + if message != "": + if self.options.verbose: + print("Files ", produced, " and ", expected, " differ:", file=sys.stderr) + print(message, file=sys.stderr) + + return result + + def compare_compiled_files(self): + files = os.listdir(self.outputdir) + dirname = os.path.dirname(self.options.p4filename) + expected_dirname = dirname + "_outputs" + for file in files: + _, extension = os.path.splitext(file) + # Skip generated object files + if extension == ".o": + continue + if self.options.verbose: + print("Checking", file) + produced = self.outputdir + "/" + file + expected = expected_dirname + "/" + file + if not os.path.isfile(expected): + if self.options.verbose: + print("Expected file does not exist; creating", expected) + shutil.copy2(produced, expected) + else: + result = self.__compare_compiled_files(produced, expected) + if result != testutils.SUCCESS: + return result + + return testutils.SUCCESS + + def _kill_processes(self, pids): + for pid in pids: + # kill pidess, 15 is SIGTERM + result = self.virtme.run(f"kill -s 15 {pid}") + if result != testutils.SUCCESS: + return result.returncode + + return testutils.SUCCESS + + def load_filter_cmd(self, ns): + # Load the specified eBPF objects to "block" ingress + # As a side-effect, this may create maps in /sys/fs/bpf/ + + cmd = ( + "{tc} filter add " + f"block {ns.block_num} ingress protocol all prio 10 " + f"p4 pname {self.base_template} " + "action bpf obj {introspection}/" + f"{self.base_template}_parser.o section p4tc/parse " + "action bpf obj {introspection}/" + f"{self.base_template}_control_blocks.o section p4tc/main" + ) + return cmd + + def spawn_bridge(self): + return self.bridge.spawn_bridge() + + def boot(self): + return self.virtme.boot() + + def _load_rules(self, rules_file): + rules_file_basename = os.path.basename(rules_file) + + return self.virtme.run_intros("{introspection}/" + f"{rules_file_basename}") + + def load_rules(self, rules_file): + if rules_file and os.path.isfile(rules_file): + return self._load_rules(rules_file) + + return testutils.SUCCESS + + def parse_stf_files(self, stffile): + """Parses the stf file and creates a .pcap file with input packets. + It also adds the expected output packets per interface to a global + dictionary. + After parsing the necessary information, it sends the packets""" + pipe_name, _ = os.path.splitext(os.path.basename(stffile)) + rules_file = None + with open(stffile) as raw_stf: + input_pkts, cmds, self.expected = parse_stf_file(raw_stf, pipe_name) + if cmds: + rules_file = f"{self.outputdir}/{self.base_template}.rules" + json_file = f"{self.outputdir}/{self.base_template}.json" + build_runtime_file(rules_file, json_file, cmds, self.virtme.ns.port_mapping) + return input_pkts, rules_file + + def _init_tcpdump_listeners(self): + # Listen to packets with tcpdump on all the ports of the ns + pids = [] + for i, port in enumerate(self.virtme.port_mapping): + outfile_name = self.filename(self.virtme.ns.mnt_dir, i, "out") + pid = self.virtme.run_tcpdump(outfile_name, port) + if pid == -1: + return [] + pids.append(pid) + + # Wait for tcpdump to initialise + time.sleep(2) + + return pids + + def send_packets(self, input_pkts): + return self._send_pcap_files(input_pkts) + + def run(self, testfile): + # Root is necessary to load ebpf into the kernel + if not testutils.check_root(): + testutils.log.warning("This test requires root privileges; skipping execution.") + return testutils.SKIPPED + + # Create the namespace and the central testing bridge + result = self.virtme.ns_init() + if result != testutils.SUCCESS: + return result + # Load template script + self.base_template = os.path.basename(self.template) + result = self.virtme.run_intros("{introspection}/" + f"{self.base_template}.template") + if result != testutils.SUCCESS: + return result + + input_pkts, rules_file = self.parse_stf_files(testfile) + + result = self.load_rules(rules_file) + if result != testutils.SUCCESS: + return result + + result = self.virtme.run_intros(self.load_filter_cmd(self.virtme.ns)) + if result != testutils.SUCCESS: + return result + + pids = self._init_tcpdump_listeners() + + result = self.send_packets(input_pkts) + if result != testutils.SUCCESS: + return result + + time.sleep(2) + + result = self._kill_processes(pids) + + time.sleep(1) + + return result + + def check_outputs(self): + """Checks if the output of the filter matches expectations""" + testutils.log.info("Comparing outputs") + direction = "out" + for file in glob(self.filename(self.runtimedir, "*", direction)): + testutils.log.info("Checking file %s", file) + interface = self.interface_of_filename(file) + if os.stat(file).st_size == 0: + packets = [] + else: + try: + packets = scapy_util.rdpcap(file) + except Exception as e: + testutils.log.error("Corrupt pcap file %s\n%s", file, e) + return testutils.FAILURE + + if interface not in self.expected: + expected = [] + else: + # Check for expected packets. + if self.expected[interface]["any"]: + if self.expected[interface]["pkts"]: + testutils.log.error( + ( + "Interface %s has both expected with packets and without", + interface, + ) + ) + continue + expected = self.expected[interface]["pkts"] + if len(expected) != len(packets): + testutils.log.error( + "Expected %s packets on port %s got %s", + len(expected), + interface, + len(packets), + ) + return testutils.FAILURE + for idx, expected_pkt in enumerate(expected): + cmp = testutils.compare_pkt(expected_pkt, bytes(packets[idx])) + if cmp != testutils.SUCCESS: + testutils.log.error("Packet %s on port %s differs", idx, interface) + return cmp + # Remove successfully checked interfaces + if interface in self.expected: + del self.expected[interface] + if len(self.expected) != 0: + # Didn't find all the expects we were expecting + testutils.log.error("Expected packets on port(s) %s not received", self.expected.keys()) + return testutils.FAILURE + testutils.log.info("All went well.") + return testutils.SUCCESS diff --git a/backends/tofino/bf-p4c/arch/fromv1.0/programStructure.cpp b/backends/tofino/bf-p4c/arch/fromv1.0/programStructure.cpp index 363aca7a326..5bb2ee555bb 100644 --- a/backends/tofino/bf-p4c/arch/fromv1.0/programStructure.cpp +++ b/backends/tofino/bf-p4c/arch/fromv1.0/programStructure.cpp @@ -2614,7 +2614,7 @@ bool CollectDigestFields::preorder(const IR::Primitive *prim) { return true; } -IR::Node *FixParserPriority::preorder(IR::AssignmentStatement *assign) { +IR::Node *FixParserPriority::preorder(IR::BaseAssignmentStatement *assign) { auto left = assign->left; while (auto slice = left->to()) left = slice->e0; while (auto cast = left->to()) left = cast->expr; diff --git a/backends/tofino/bf-p4c/arch/fromv1.0/programStructure.h b/backends/tofino/bf-p4c/arch/fromv1.0/programStructure.h index bd2035b72e0..e693e6eabd7 100644 --- a/backends/tofino/bf-p4c/arch/fromv1.0/programStructure.h +++ b/backends/tofino/bf-p4c/arch/fromv1.0/programStructure.h @@ -542,7 +542,7 @@ class FixPktgenHeaderPath : public RenameFieldPath { class FixParserPriority : public Transform { public: FixParserPriority() {} - IR::Node *preorder(IR::AssignmentStatement *assign) override; + IR::Node *preorder(IR::BaseAssignmentStatement *assign) override; }; class ParserCounterSelectCaseConverter : public Transform { @@ -1170,7 +1170,7 @@ class ModifyParserForChecksum : public Modifier { if (!inst->is()) return; auto em = inst->to(); if (em->actualExternType->name != "Checksum" || em->method->name != "update") return; - auto assign = findOrigCtxt(); + auto assign = findOrigCtxt(); if (assign == nullptr) return; auto destField = assign->left; // auto parser = findContext(); diff --git a/backends/tofino/bf-p4c/arch/remove_set_metadata.cpp b/backends/tofino/bf-p4c/arch/remove_set_metadata.cpp index d2214a7b4ad..5e34a9efa76 100644 --- a/backends/tofino/bf-p4c/arch/remove_set_metadata.cpp +++ b/backends/tofino/bf-p4c/arch/remove_set_metadata.cpp @@ -37,7 +37,8 @@ bool isEgressIntrinsicHeader(const IR::Type *type) { return false; } -const IR::AssignmentStatement *RemoveSetMetadata::preorder(IR::AssignmentStatement *assignment) { +const IR::BaseAssignmentStatement *RemoveSetMetadata::preorder( + IR::BaseAssignmentStatement *assignment) { prune(); auto *parser = findContext(); diff --git a/backends/tofino/bf-p4c/arch/remove_set_metadata.h b/backends/tofino/bf-p4c/arch/remove_set_metadata.h index b864ec31f23..062d6070809 100644 --- a/backends/tofino/bf-p4c/arch/remove_set_metadata.h +++ b/backends/tofino/bf-p4c/arch/remove_set_metadata.h @@ -57,7 +57,7 @@ namespace BFN { struct RemoveSetMetadata : public Transform { RemoveSetMetadata(P4::ReferenceMap *refMap, P4::TypeMap *typeMap); - const IR::AssignmentStatement *preorder(IR::AssignmentStatement *assignment); + const IR::BaseAssignmentStatement *preorder(IR::BaseAssignmentStatement *assignment); private: P4::ReferenceMap *refMap; diff --git a/backends/tofino/bf-p4c/arch/v1model.cpp b/backends/tofino/bf-p4c/arch/v1model.cpp index e6eb2e8aa6e..b02b32930f4 100644 --- a/backends/tofino/bf-p4c/arch/v1model.cpp +++ b/backends/tofino/bf-p4c/arch/v1model.cpp @@ -1878,7 +1878,7 @@ class ConstructSymbolTable : public Inspector { if (it == toTranslateInParser.end()) return; if (auto type = expr->type->to()) { if (type->path->name == "ingress_parser_control_signals") { - if (auto stmt = findContext()) { + if (auto stmt = findContext()) { if (node->member == "parser_counter") { ParserCounterConverter cvt(structure); structure->_map.emplace(stmt, stmt->apply(cvt)); diff --git a/backends/tofino/bf-p4c/control-plane/bfruntime_arch_handler.h b/backends/tofino/bf-p4c/control-plane/bfruntime_arch_handler.h index 7ea94372a2a..5e8aaee326e 100644 --- a/backends/tofino/bf-p4c/control-plane/bfruntime_arch_handler.h +++ b/backends/tofino/bf-p4c/control-plane/bfruntime_arch_handler.h @@ -810,7 +810,7 @@ class BFRuntimeArchHandlerCommon : public P4::ControlPlaneAPI::P4RuntimeArchHand } void collectAssignmentStatement(P4RuntimeSymbolTableIface *, - const IR::AssignmentStatement *) override {} + const IR::BaseAssignmentStatement *) override {} void collectExternMethod(P4RuntimeSymbolTableIface *, const P4::ExternMethod *) override {} diff --git a/backends/tofino/bf-p4c/midend.cpp b/backends/tofino/bf-p4c/midend.cpp index a3e425b669b..85467f960be 100644 --- a/backends/tofino/bf-p4c/midend.cpp +++ b/backends/tofino/bf-p4c/midend.cpp @@ -301,7 +301,7 @@ bool skipRegisterActionOutput(const Visitor::Context *ctxt, const IR::Expression auto params = method->parameters->to(); if (!params) return true; - auto assign = dynamic_cast(ctxt->parent->node); + auto assign = dynamic_cast(ctxt->parent->node); if (!assign) return true; auto dest_path = assign->left->to(); diff --git a/backends/tofino/bf-p4c/midend/action_synthesis_policy.h b/backends/tofino/bf-p4c/midend/action_synthesis_policy.h index e5f979781aa..e9008ca6275 100644 --- a/backends/tofino/bf-p4c/midend/action_synthesis_policy.h +++ b/backends/tofino/bf-p4c/midend/action_synthesis_policy.h @@ -71,7 +71,7 @@ class ActionSynthesisPolicy : public P4::ActionSynthesisPolicy { if (isWrite()) writes.insert(m->toString()); return false; } - bool preorder(const IR::AssignmentStatement *assign) { + bool preorder(const IR::BaseAssignmentStatement *assign) { // special case -- ignore writing the result of a 'hash.get' call to a var, // as we can use that directly in the same action (hash is computed in ixbar hash) if (auto *mc = assign->right->to()) { diff --git a/backends/tofino/bf-p4c/midend/check_header_alignment.cpp b/backends/tofino/bf-p4c/midend/check_header_alignment.cpp index f9cf33da59b..f5dd962c7bb 100644 --- a/backends/tofino/bf-p4c/midend/check_header_alignment.cpp +++ b/backends/tofino/bf-p4c/midend/check_header_alignment.cpp @@ -27,7 +27,7 @@ namespace BFN { -bool CheckPadAssignment::preorder(const IR::AssignmentStatement *statement) { +bool CheckPadAssignment::preorder(const IR::BaseAssignmentStatement *statement) { auto member = statement->left->to(); if (!member) return false; auto header_type = dynamic_cast(member->expr->type); diff --git a/backends/tofino/bf-p4c/midend/check_header_alignment.h b/backends/tofino/bf-p4c/midend/check_header_alignment.h index c6ce4276903..c19277fd0c4 100644 --- a/backends/tofino/bf-p4c/midend/check_header_alignment.h +++ b/backends/tofino/bf-p4c/midend/check_header_alignment.h @@ -41,7 +41,7 @@ namespace BFN { */ class CheckPadAssignment final : public Inspector { private: - bool preorder(const IR::AssignmentStatement *statement) override; + bool preorder(const IR::BaseAssignmentStatement *statement) override; public: CheckPadAssignment() {} diff --git a/backends/tofino/bf-p4c/midend/copy_header.cpp b/backends/tofino/bf-p4c/midend/copy_header.cpp index 80625f6fc27..8b560f3e504 100644 --- a/backends/tofino/bf-p4c/midend/copy_header.cpp +++ b/backends/tofino/bf-p4c/midend/copy_header.cpp @@ -196,7 +196,7 @@ const IR::Node *DoCopyHeaders::postorder(IR::MethodCallExpression *mc) { auto validtype = IR::Type::Bits::get(1); auto member = new IR::Member(mc->srcInfo, validtype, mem->expr, "$valid"); - if (findContext() || findContext()) { + if (findContext() || findContext()) { // Maintain the Boolean type of the expression - a ReinterpretCast is not enough! // But if it is already a ReinterpretCast, don't add an expression. if (!getContext()->node->is()) diff --git a/backends/tofino/bf-p4c/midend/desugar_varbit_extract.cpp b/backends/tofino/bf-p4c/midend/desugar_varbit_extract.cpp index 0af5255a192..cd989668274 100644 --- a/backends/tofino/bf-p4c/midend/desugar_varbit_extract.cpp +++ b/backends/tofino/bf-p4c/midend/desugar_varbit_extract.cpp @@ -83,7 +83,7 @@ struct ErrorOnUnsupportedVarbitUse : public Inspector { // TODO: When we enable assignment of varbits, we need to make sure the dead emit elimination // works correctly even with assignment. - bool preorder(const IR::AssignmentStatement *asgn) override { + bool preorder(const IR::BaseAssignmentStatement *asgn) override { if (asgn->right->type->is()) { fatal_error(ErrorType::ERR_UNSUPPORTED_ON_TARGET, "%1%: cannot assign varbit field. The compiler currently does not " diff --git a/backends/tofino/bf-p4c/midend/remove_action_params.h b/backends/tofino/bf-p4c/midend/remove_action_params.h index c05227080ce..c5940d47c4a 100644 --- a/backends/tofino/bf-p4c/midend/remove_action_params.h +++ b/backends/tofino/bf-p4c/midend/remove_action_params.h @@ -48,8 +48,8 @@ class DoRemoveActionParametersTofino : public P4::DoRemoveActionParameters { for (auto abc : action->body->components) { /* iterate thought all statements, and find the uses of smeta */ /* if the only use is in mark_to_drop -- that's not a use! */ - if (abc->is()) { - auto as = abc->to(); + if (abc->is()) { + auto as = abc->to(); // TODO: Handle assignment statements with expressions if (p->name == as->left->toString()) paramUses++; diff --git a/backends/tofino/bf-p4c/parde/extract_deparser.h b/backends/tofino/bf-p4c/parde/extract_deparser.h index e6cfd2bc2d9..6c9421f17be 100644 --- a/backends/tofino/bf-p4c/parde/extract_deparser.h +++ b/backends/tofino/bf-p4c/parde/extract_deparser.h @@ -87,6 +87,7 @@ class ExtractDeparser : public DeparserInspector { IR::ID getTnaParamName(const IR::BFN::TnaDeparser *deparser, IR::ID orig_name); bool preorder(const IR::Annotation *annot) override; + bool preorder(const IR::BaseAssignmentStatement *) override { BUG("not handled"); } bool preorder(const IR::AssignmentStatement *stmt) override; void postorder(const IR::MethodCallExpression *mc) override; void end_apply() override; diff --git a/backends/tofino/bf-p4c/parde/extract_parser.cpp b/backends/tofino/bf-p4c/parde/extract_parser.cpp index 0823317bba8..1b3b2d54390 100644 --- a/backends/tofino/bf-p4c/parde/extract_parser.cpp +++ b/backends/tofino/bf-p4c/parde/extract_parser.cpp @@ -60,7 +60,7 @@ struct GetHeaderStackIndex : public Inspector { bool preorder(const IR::HeaderStackItemRef *ref) override { if (ignore_valid) { - auto stmt = findContext(); + auto stmt = findContext(); if (stmt) { auto lhs = stmt->left->to(); // Ignore any prior change to $valid diff --git a/backends/ubpf/ubpfControl.h b/backends/ubpf/ubpfControl.h index 5ec64ac37f3..1f1dc83973a 100644 --- a/backends/ubpf/ubpfControl.h +++ b/backends/ubpf/ubpfControl.h @@ -42,6 +42,7 @@ class UBPFControlBodyTranslator : public EBPF::ControlBodyTranslator { bool preorder(const IR::PathExpression *expression) override; bool preorder(const IR::MethodCallStatement *s) override; bool preorder(const IR::MethodCallExpression *expression) override; + bool preorder(const IR::BaseAssignmentStatement *a) override { return notSupported(a); } bool preorder(const IR::AssignmentStatement *a) override; bool preorder(const IR::BlockStatement *s) override; bool preorder(const IR::ExitStatement *) override; diff --git a/backends/ubpf/ubpfDeparser.cpp b/backends/ubpf/ubpfDeparser.cpp index 72216f1fdbb..f68956d3456 100644 --- a/backends/ubpf/ubpfDeparser.cpp +++ b/backends/ubpf/ubpfDeparser.cpp @@ -59,7 +59,9 @@ class OutHeaderSize final : public EBPF::CodeGenInspector { } bool preorder(const IR::SwitchStatement *statement) override { return illegal(statement); } bool preorder(const IR::IfStatement *statement) override { return illegal(statement); } - bool preorder(const IR::AssignmentStatement *statement) override { return illegal(statement); } + bool preorder(const IR::BaseAssignmentStatement *statement) override { + return illegal(statement); + } bool preorder(const IR::ReturnStatement *statement) override { return illegal(statement); } bool preorder(const IR::ExitStatement *statement) override { return illegal(statement); } bool preorder(const IR::MethodCallStatement *statement) override { diff --git a/backends/ubpf/ubpfDeparser.h b/backends/ubpf/ubpfDeparser.h index babcd31e654..90099dc98df 100644 --- a/backends/ubpf/ubpfDeparser.h +++ b/backends/ubpf/ubpfDeparser.h @@ -50,7 +50,7 @@ class UBPFDeparserTranslationVisitor : public EBPF::CodeGenInspector { } bool preorder(const IR::MethodCallExpression *expression) override; - bool preorder(const IR::AssignmentStatement *a) override { return notSupported(a); }; + bool preorder(const IR::BaseAssignmentStatement *a) override { return notSupported(a); }; bool preorder(const IR::ExitStatement *s) override { return notSupported(s); }; bool preorder(UNUSED const IR::BlockStatement *s) override { return true; }; bool preorder(const IR::ReturnStatement *s) override { return notSupported(s); }; diff --git a/backends/ubpf/ubpfParser.cpp b/backends/ubpf/ubpfParser.cpp index cdd09c300bb..939f2a3ac56 100644 --- a/backends/ubpf/ubpfParser.cpp +++ b/backends/ubpf/ubpfParser.cpp @@ -61,6 +61,7 @@ class UBPFStateTranslationVisitor : public EBPF::CodeGenInspector { visit(stat->methodCall); return false; } + bool preorder(const IR::BaseAssignmentStatement *stat) override { return notSupported(stat); } bool preorder(const IR::AssignmentStatement *stat) override; }; } // namespace diff --git a/cmake/Linters.cmake b/cmake/Linters.cmake index ecab52b80e5..45ea1e8abdd 100644 --- a/cmake/Linters.cmake +++ b/cmake/Linters.cmake @@ -139,6 +139,7 @@ list(FILTER P4C_PYTHON_LINT_LIST EXCLUDE REGEX "backends/p4tools/submodules") list(FILTER P4C_PYTHON_LINT_LIST EXCLUDE REGEX "backends/ebpf/runtime/contrib/libbpf") list(FILTER P4C_PYTHON_LINT_LIST EXCLUDE REGEX "backends/tofino/third_party") list(FILTER P4C_PYTHON_LINT_LIST EXCLUDE REGEX "tools/cpplint.py") +list(FILTER P4C_PYTHON_LINT_LIST EXCLUDE REGEX "backends/tc/runtime") add_black_files(${P4C_SOURCE_DIR} "${P4C_PYTHON_LINT_LIST}") diff --git a/control-plane/p4RuntimeArchHandler.h b/control-plane/p4RuntimeArchHandler.h index afe38e1289a..2f4d98d2fe9 100644 --- a/control-plane/p4RuntimeArchHandler.h +++ b/control-plane/p4RuntimeArchHandler.h @@ -153,7 +153,7 @@ class P4RuntimeArchHandlerIface { const IR::ExternBlock *externBlock) = 0; /// Collects architecture-specific used in assignment statements virtual void collectAssignmentStatement(P4RuntimeSymbolTableIface *symbols, - const IR::AssignmentStatement *assign) = 0; + const IR::BaseAssignmentStatement *assign) = 0; /// Collects architecture-specific @externMethod instance in @symbols table. virtual void collectExternMethod(P4RuntimeSymbolTableIface *symbols, const P4::ExternMethod *externMethod) = 0; diff --git a/control-plane/p4RuntimeArchStandard.h b/control-plane/p4RuntimeArchStandard.h index 8bff90d615f..91cc0cb6c3c 100644 --- a/control-plane/p4RuntimeArchStandard.h +++ b/control-plane/p4RuntimeArchStandard.h @@ -586,7 +586,7 @@ class P4RuntimeArchHandlerCommon : public P4RuntimeArchHandlerIface { } void collectAssignmentStatement(P4RuntimeSymbolTableIface *, - const IR::AssignmentStatement *) override {} + const IR::BaseAssignmentStatement *) override {} void collectExternMethod(P4RuntimeSymbolTableIface *, const P4::ExternMethod *) override {} diff --git a/control-plane/p4RuntimeSymbolTable.cpp b/control-plane/p4RuntimeSymbolTable.cpp index da157747a52..d113d379c9b 100644 --- a/control-plane/p4RuntimeSymbolTable.cpp +++ b/control-plane/p4RuntimeSymbolTable.cpp @@ -106,8 +106,8 @@ void collectControlSymbols(P4RuntimeSymbolTable &symbols, P4RuntimeArchHandlerIf }); // Collect any use of something in an assignment statement - forAllMatching( - control->body, [&](const IR::AssignmentStatement *assign) { + forAllMatching( + control->body, [&](const IR::BaseAssignmentStatement *assign) { archHandler->collectAssignmentStatement(&symbols, assign); }); diff --git a/frontends/CMakeLists.txt b/frontends/CMakeLists.txt index dc44cf8ade6..7262f2569b2 100644 --- a/frontends/CMakeLists.txt +++ b/frontends/CMakeLists.txt @@ -123,6 +123,7 @@ set (P4_FRONTEND_HDRS p4/parserControlFlow.h p4/reassociation.h p4/redundantParsers.h + p4/removeOpAssign.h p4/removeParameters.h p4/removeReturns.h p4/reservedWords.h diff --git a/frontends/common/constantFolding.cpp b/frontends/common/constantFolding.cpp index 2ba8c18ae94..6c5c3b57960 100644 --- a/frontends/common/constantFolding.cpp +++ b/frontends/common/constantFolding.cpp @@ -189,7 +189,7 @@ const IR::Node *DoConstantFolding::postorder(IR::Declaration_Constant *d) { return d; } -const IR::Node *DoConstantFolding::preorder(IR::AssignmentStatement *statement) { +const IR::Node *DoConstantFolding::preorder(IR::BaseAssignmentStatement *statement) { assignmentTarget = true; visit(statement->left); assignmentTarget = false; diff --git a/frontends/common/constantFolding.h b/frontends/common/constantFolding.h index 14f02d606b3..23648fd39d6 100644 --- a/frontends/common/constantFolding.h +++ b/frontends/common/constantFolding.h @@ -171,7 +171,7 @@ class DoConstantFolding : public Transform, public ResolutionContext { const IR::Node *postorder(IR::Type_Varbits *type) override; const IR::Node *postorder(IR::SelectExpression *e) override; const IR::Node *postorder(IR::IfStatement *statement) override; - const IR::Node *preorder(IR::AssignmentStatement *statement) override; + const IR::Node *preorder(IR::BaseAssignmentStatement *statement) override; const IR::Node *preorder(IR::ArrayIndex *e) override; const IR::Node *preorder(IR::SwitchCase *c) override; const IR::BlockStatement *preorder(IR::BlockStatement *bs) override { diff --git a/frontends/p4/def_use.cpp b/frontends/p4/def_use.cpp index 452e38f3b01..bd32a8093c7 100644 --- a/frontends/p4/def_use.cpp +++ b/frontends/p4/def_use.cpp @@ -981,7 +981,7 @@ bool ComputeWriteSet::preorder(const IR::EmptyStatement *) { return setDefinitions(currentDefinitions); } -bool ComputeWriteSet::preorder(const IR::AssignmentStatement *statement) { +bool ComputeWriteSet::preorder(const IR::BaseAssignmentStatement *statement) { LOG3("CWS Visiting " << dbp(statement) << " " << statement); if (currentDefinitions->isUnreachable()) return setDefinitions(currentDefinitions); lhs = true; diff --git a/frontends/p4/def_use.h b/frontends/p4/def_use.h index 82fe4c26a87..3d81e0683fa 100644 --- a/frontends/p4/def_use.h +++ b/frontends/p4/def_use.h @@ -429,7 +429,7 @@ class ProgramPoint : public IHasDbPrint { } auto l = stack.back(); if (l != nullptr && - (l->is() || l->is())) + (l->is() || l->is())) out << "[[" << l << "]]"; } } @@ -657,7 +657,7 @@ class ComputeWriteSet : public Inspector, public IHasDbPrint { bool preorder(const IR::P4Action *action) override; bool preorder(const IR::P4Table *table) override; bool preorder(const IR::Function *function) override; - bool preorder(const IR::AssignmentStatement *statement) override; + bool preorder(const IR::BaseAssignmentStatement *statement) override; bool preorder(const IR::ReturnStatement *statement) override; bool preorder(const IR::ExitStatement *statement) override; bool preorder(const IR::BreakStatement *statement) override; diff --git a/frontends/p4/frontend.cpp b/frontends/p4/frontend.cpp index b2bd068ccfa..bbeaa5bd515 100644 --- a/frontends/p4/frontend.cpp +++ b/frontends/p4/frontend.cpp @@ -50,6 +50,7 @@ limitations under the License. #include "parserControlFlow.h" #include "reassociation.h" #include "redundantParsers.h" +#include "removeOpAssign.h" #include "removeParameters.h" #include "removeReturns.h" #include "resetHeaders.h" @@ -216,6 +217,7 @@ const IR::P4Program *FrontEnd::run(const CompilerOptions &options, const IR::P4P new MoveDeclarations(), // Move all local declarations to the beginning new MoveInitializers(), new SideEffectOrdering(&typeMap, policy->skipSideEffectOrdering()), + policy->removeOpAssign() ? new RemoveOpAssign() : nullptr, new SimplifyControlFlow(&typeMap), new SimplifySwitch(&typeMap), new MoveDeclarations(), // Move all local declarations to the beginning diff --git a/frontends/p4/frontend.h b/frontends/p4/frontend.h index 14ad2f345a1..8b1ddf257e7 100644 --- a/frontends/p4/frontend.h +++ b/frontends/p4/frontend.h @@ -43,6 +43,11 @@ class FrontEndPolicy : public RemoveUnusedPolicy { /// @returns Defaults to nullptr. virtual ParseAnnotations *getParseAnnotations() const { return nullptr; } + /// Indicates whether OpAssignmentExpressions should be expanded and replaced + /// by simple assignments. This depends on SideEffectOrdering to be correct, so + /// should probably be false if skipSideEffectOrdering is true. + virtual bool removeOpAssign() const { return true; } + /// Indicates whether the side-effect-ordering pass should be skipped. /// @returns Defaults to false. // TODO: This should probably not be allowed to be skipped at all. diff --git a/frontends/p4/functionsInlining.cpp b/frontends/p4/functionsInlining.cpp index c2893055eb2..525f569e11a 100644 --- a/frontends/p4/functionsInlining.cpp +++ b/frontends/p4/functionsInlining.cpp @@ -38,7 +38,8 @@ void DiscoverFunctionsInlining::postorder(const IR::MethodCallExpression *mce) { CHECK_NULL(stat); BUG_CHECK( - bool(RTTI::isAny(stat)), + bool(RTTI::isAny( + stat)), "%1%: unexpected statement with call", stat); if (const auto *ifStat = stat->to()) { @@ -173,8 +174,8 @@ void FunctionsInliner::dumpReplacementMap() const { LOG2("\t" << it.first << " with " << it.second.first << " via " << it.second.second); } -const IR::Node *FunctionsInliner::preorder(IR::AssignmentStatement *statement) { - auto orig = getOriginal(); +const IR::Node *FunctionsInliner::preorder(IR::BaseAssignmentStatement *statement) { + auto orig = getOriginal(); LOG2("Visiting " << dbp(orig)); auto replMap = getReplacementMap(); @@ -318,7 +319,7 @@ const IR::Statement *FunctionsInliner::inlineBefore(const IR::Node *calleeNode, body.push_back(copyout); } - if (auto assign = statement->to()) { + if (auto assign = statement->to()) { // copy the return value CHECK_NULL(retExpr); // If we can replace RHS immediately, do it here, otherwise add return diff --git a/frontends/p4/functionsInlining.h b/frontends/p4/functionsInlining.h index 9601c0c514c..100a21b216d 100644 --- a/frontends/p4/functionsInlining.h +++ b/frontends/p4/functionsInlining.h @@ -85,7 +85,7 @@ class FunctionsInliner : public AbstractInliner + const IR::Node *doit(T *as) { + prune(); + BUG_CHECK(!SideEffects::check(as->left, this), "side effects in LHS of %s", as); + return new IR::AssignmentStatement(as->srcInfo, as->left->apply(CloneExpressions()), + new typename T::BinOp(as->left, as->right)); + } + +#define PREORDER(OP) \ + const IR::Node *preorder(IR::OP *as) override { return doit(as); } + PREORDER(MulAssign) + PREORDER(DivAssign) + PREORDER(ModAssign) + PREORDER(AddAssign) + PREORDER(SubAssign) + PREORDER(AddSatAssign) + PREORDER(SubSatAssign) + PREORDER(ShlAssign) + PREORDER(ShrAssign) + PREORDER(BAndAssign) + PREORDER(BOrAssign) + PREORDER(BXorAssign) +#undef PREORDER + + const IR::Node *preorder(IR::AssignmentStatement *s) override { + prune(); + return s; + } + const IR::Node *preorder(IR::Expression *e) override { + prune(); + return e; + } + const IR::Node *preorder(IR::Annotation *a) override { + prune(); + return a; + } +}; + +} // namespace P4 + +#endif /* FRONTENDS_P4_REMOVEOPASSIGN_H_ */ diff --git a/frontends/p4/sideEffects.cpp b/frontends/p4/sideEffects.cpp index 191ea387255..0393dcd2229 100644 --- a/frontends/p4/sideEffects.cpp +++ b/frontends/p4/sideEffects.cpp @@ -117,7 +117,7 @@ const IR::Node *DoSimplifyExpressions::preorder(IR::Member *expression) { if (isIfContext(getContext())) { /* if the hit/miss test is directly in an "if", don't bother cloning it * as that will just create a redundant table later */ - } else if (getParent()) { + } else if (getParent()) { /* already assigning it somewhere -- no need to add another copy */ } else { BUG_CHECK(type->is(), "%1%: not boolean", type); @@ -558,7 +558,7 @@ const IR::Node *DoSimplifyExpressions::preorder(IR::MethodCallExpression *mce) { } else if (tbl_apply) { typeMap->setType(mce, type); rv = mce; - } else if (getParent() && copyBack.empty()) { + } else if (getParent() && copyBack.empty()) { /* no need for an extra copy as there's no out args to copy back afterwards */ typeMap->setType(mce, type); rv = mce; @@ -619,7 +619,7 @@ const IR::Node *DoSimplifyExpressions::postorder(IR::ParserState *state) { return state; } -const IR::Node *DoSimplifyExpressions::postorder(IR::AssignmentStatement *statement) { +const IR::Node *DoSimplifyExpressions::postorder(IR::BaseAssignmentStatement *statement) { if (statements.empty()) return statement; statements.push_back(statement); auto block = new IR::BlockStatement(statements); diff --git a/frontends/p4/sideEffects.h b/frontends/p4/sideEffects.h index a9a5a33a612..11db9242c16 100644 --- a/frontends/p4/sideEffects.h +++ b/frontends/p4/sideEffects.h @@ -104,7 +104,7 @@ class SideEffects : public Inspector { /// @return true if the expression may have side-effects. static bool check(const IR::Expression *expression, const Visitor *calledBy, - DeclarationLookup *refMap, TypeMap *typeMap, + DeclarationLookup *refMap = nullptr, TypeMap *typeMap = nullptr, const Visitor::Context *ctxt = nullptr) { SideEffects se(refMap, typeMap); se.setCalledBy(calledBy); @@ -218,7 +218,7 @@ class DoSimplifyExpressions : public Transform, P4WriteContext, public Resolutio const IR::Node *postorder(IR::P4Control *control) override; const IR::Node *postorder(IR::P4Action *action) override; const IR::Node *postorder(IR::ParserState *state) override; - const IR::Node *postorder(IR::AssignmentStatement *statement) override; + const IR::Node *postorder(IR::BaseAssignmentStatement *statement) override; const IR::Node *postorder(IR::MethodCallStatement *statement) override; const IR::Node *postorder(IR::ReturnStatement *statement) override; const IR::Node *preorder(IR::SwitchStatement *statement) override; @@ -233,7 +233,7 @@ class DoSimplifyExpressions : public Transform, P4WriteContext, public Resolutio class TableInsertions { public: std::vector declarations; - std::vector statements; + std::vector statements; }; /** @@ -308,7 +308,7 @@ class KeySideEffect : public Transform, public ResolutionContext { const IR::Node *postorder(IR::SwitchStatement *statement) override { return doStatement(statement, statement->expression, getContext()); } - const IR::Node *postorder(IR::AssignmentStatement *statement) override { + const IR::Node *postorder(IR::BaseAssignmentStatement *statement) override { return doStatement(statement, statement->right, getContext()); } const IR::Node *postorder(IR::KeyElement *element) override; diff --git a/frontends/p4/simplifyDefUse.cpp b/frontends/p4/simplifyDefUse.cpp index b593728069d..bfa9c969fe9 100644 --- a/frontends/p4/simplifyDefUse.cpp +++ b/frontends/p4/simplifyDefUse.cpp @@ -57,7 +57,7 @@ class HasUses { if (!isActive()) return false; if (previous.isBeforeStart()) return false; auto last = previous.last(); - if (auto *assign_stmt = last->to()) { + if (auto *assign_stmt = last->to()) { if (auto *slice_stmt = assign_stmt->left->to()) { // two slice stmts writing to same location // skip use of previous if it gets overwritten @@ -790,10 +790,11 @@ class FindUninitialized : public Inspector { } } - bool preorder(const IR::AssignmentStatement *statement) override { + bool preorder(const IR::BaseAssignmentStatement *statement) override { Log::TempIndent indent; LOG3("FU Visiting " << dbp(statement) << " " << statement << indent); if (!unreachable) { + if (statement->is()) visit(statement->left); lhs = true; visit(statement->left); checkHeaderFieldWrite(statement->left, statement->left); @@ -1382,7 +1383,7 @@ class FindUninitialized : public Inspector { bool preorder(const IR::AbstractSlice *expression) override { LOG3("FU Visiting [" << expression->id << "]: " << expression); - auto *slice_stmt = findContext(); + auto *slice_stmt = findContext(); auto *slice = expression->to(); if (slice_stmt != nullptr && lhs && slice) { // track this slice statement @@ -1471,7 +1472,7 @@ class RemoveUnused : public Transform { CHECK_NULL(typeMap); setName("RemoveUnused"); } - const IR::Node *postorder(IR::AssignmentStatement *statement) override { + const IR::Node *postorder(IR::BaseAssignmentStatement *statement) override { if (!hasUses.hasUses(getOriginal())) { Log::TempIndent indent; LOG3("Removing statement " << getOriginal() << " " << statement << indent); diff --git a/frontends/p4/toP4/toP4.cpp b/frontends/p4/toP4/toP4.cpp index 0ef1a2a6984..5ba76db56ef 100644 --- a/frontends/p4/toP4/toP4.cpp +++ b/frontends/p4/toP4/toP4.cpp @@ -1091,6 +1091,17 @@ bool ToP4::preorder(const IR::AssignmentStatement *a) { return false; } +bool ToP4::preorder(const IR::OpAssignmentStatement *a) { + dump(2); + visit(a->left); + builder.append(" "); + builder.append(a->getStringOp()); + builder.append("= "); + visit(a->right); + builder.endOfStatement(); + return false; +} + bool ToP4::preorder(const IR::BlockStatement *s) { dump(1); if (printAnnotations(s)) builder.spc(); diff --git a/frontends/p4/toP4/toP4.h b/frontends/p4/toP4/toP4.h index 6ae480ee1e1..d719c13e108 100644 --- a/frontends/p4/toP4/toP4.h +++ b/frontends/p4/toP4/toP4.h @@ -220,6 +220,7 @@ class ToP4 : public Inspector, ResolutionContext { // statements bool preorder(const IR::AssignmentStatement *s) override; + bool preorder(const IR::OpAssignmentStatement *s) override; bool preorder(const IR::BlockStatement *s) override; bool preorder(const IR::MethodCallStatement *s) override; bool preorder(const IR::EmptyStatement *s) override; diff --git a/frontends/p4/typeChecking/readOnlyTypeInference.cpp b/frontends/p4/typeChecking/readOnlyTypeInference.cpp index bf12c0bd5a4..db825a61e6f 100644 --- a/frontends/p4/typeChecking/readOnlyTypeInference.cpp +++ b/frontends/p4/typeChecking/readOnlyTypeInference.cpp @@ -140,6 +140,9 @@ DEFINE_POSTORDER(IR::ReturnStatement) DEFINE_POSTORDER(IR::IfStatement) DEFINE_POSTORDER(IR::SwitchStatement) DEFINE_POSTORDER(IR::AssignmentStatement) +DEFINE_POSTORDER(IR::OpAssignmentStatement) +DEFINE_POSTORDER(IR::ShlAssign) +DEFINE_POSTORDER(IR::ShrAssign) DEFINE_POSTORDER(IR::ForInStatement) DEFINE_POSTORDER(IR::ActionListElement) DEFINE_POSTORDER(IR::KeyElement) diff --git a/frontends/p4/typeChecking/typeCheckStmt.cpp b/frontends/p4/typeChecking/typeCheckStmt.cpp index e2d771278f9..f3500a3c482 100644 --- a/frontends/p4/typeChecking/typeCheckStmt.cpp +++ b/frontends/p4/typeChecking/typeCheckStmt.cpp @@ -125,9 +125,8 @@ const IR::Node *TypeInferenceBase::postorder(const IR::ReturnStatement *statemen return statement; } -const IR::Node *TypeInferenceBase::postorder(const IR::AssignmentStatement *assign) { - LOG3("TI Visiting " << dbp(getOriginal())); - auto ltype = getType(assign->left); +const IR::Node *TypeInferenceBase::common_assign(const IR::BaseAssignmentStatement *assign, + const IR::Type *ltype) { if (ltype == nullptr) return assign; if (!isLeftValue(assign->left)) { @@ -137,8 +136,45 @@ const IR::Node *TypeInferenceBase::postorder(const IR::AssignmentStatement *assi } auto newInit = assignment(assign, ltype, assign->right); - if (newInit != assign->right) - assign = new IR::AssignmentStatement(assign->srcInfo, assign->left, newInit); + if (newInit != assign->right) { + auto *clone = assign->clone(); + clone->right = newInit; + assign = clone; + } + return assign; +} + +const IR::Node *TypeInferenceBase::postorder(const IR::AssignmentStatement *assign) { + LOG3("TI Visiting " << dbp(getOriginal())); + return TypeInferenceBase::common_assign(assign, getType(assign->left)); +} + +const IR::Node *TypeInferenceBase::postorder(const IR::OpAssignmentStatement *assign) { + LOG3("TI Visiting " << dbp(getOriginal())); + auto ltype = getType(assign->left); + if (ltype && !ltype->is()) { + typeError("%1%=: cannot be applied to '%2%' with type '%3%'", assign->getStringOp(), + assign->left, ltype->toString()); + return assign; + } + return TypeInferenceBase::common_assign(assign, ltype); +} + +const IR::Node *TypeInferenceBase::shiftAssign(const IR::OpAssignmentStatement *assign) { + LOG3("TI Visiting " << dbp(getOriginal())); + auto ltype = getType(assign->left); + auto rtype = getType(assign->left); + if (!ltype) return assign; + if (!isLeftValue(assign->left)) { + typeError("Expression %1% cannot be the target of an assignment", assign->left); + LOG2(assign->left); + } else if (!ltype->is()) { + typeError("%1%=: cannot be applied to '%2%' with type '%3%'", assign->getStringOp(), + assign->left, ltype->toString()); + } else if (rtype && !rtype->is() && !rtype->is()) { + typeError("%1%=: cannot be applied with '%2%' with type '%3%'", assign->getStringOp(), + assign->right, rtype->toString()); + } return assign; } diff --git a/frontends/p4/typeChecking/typeChecker.h b/frontends/p4/typeChecking/typeChecker.h index 306c55c8403..3064c195ef2 100644 --- a/frontends/p4/typeChecking/typeChecker.h +++ b/frontends/p4/typeChecking/typeChecker.h @@ -328,7 +328,12 @@ class TypeInferenceBase : public virtual Visitor, public ResolutionContext { const IR::Node *postorder(const IR::ReturnStatement *stat); const IR::Node *postorder(const IR::IfStatement *stat); const IR::Node *postorder(const IR::SwitchStatement *stat); + const IR::Node *common_assign(const IR::BaseAssignmentStatement *stat, const IR::Type *); const IR::Node *postorder(const IR::AssignmentStatement *stat); + const IR::Node *postorder(const IR::OpAssignmentStatement *stat); + const IR::Node *shiftAssign(const IR::OpAssignmentStatement *stat); + const IR::Node *postorder(const IR::ShlAssign *stat) { return shiftAssign(stat); } + const IR::Node *postorder(const IR::ShrAssign *stat) { return shiftAssign(stat); } const IR::Node *postorder(const IR::ForInStatement *stat); const IR::Node *postorder(const IR::ActionListElement *elem); const IR::Node *postorder(const IR::KeyElement *elem); @@ -472,6 +477,9 @@ class ReadOnlyTypeInference : public virtual Inspector, public TypeInferenceBase void postorder(const IR::IfStatement *stat) override; void postorder(const IR::SwitchStatement *stat) override; void postorder(const IR::AssignmentStatement *stat) override; + void postorder(const IR::OpAssignmentStatement *stat) override; + void postorder(const IR::ShlAssign *stat) override; + void postorder(const IR::ShrAssign *stat) override; void postorder(const IR::ForInStatement *stat) override; void postorder(const IR::ActionListElement *elem) override; void postorder(const IR::KeyElement *elem) override; @@ -608,6 +616,9 @@ class TypeInference : public virtual Transform, public TypeInferenceBase { const IR::Node *postorder(IR::IfStatement *stat) override; const IR::Node *postorder(IR::SwitchStatement *stat) override; const IR::Node *postorder(IR::AssignmentStatement *stat) override; + const IR::Node *postorder(IR::OpAssignmentStatement *stat) override; + const IR::Node *postorder(IR::ShlAssign *stat) override; + const IR::Node *postorder(IR::ShrAssign *stat) override; const IR::Node *postorder(IR::ForInStatement *stat) override; const IR::Node *postorder(IR::ActionListElement *elem) override; const IR::Node *postorder(IR::KeyElement *elem) override; diff --git a/frontends/p4/typeChecking/typeInference.cpp b/frontends/p4/typeChecking/typeInference.cpp index 0a7202d6f3c..27566a37cd2 100644 --- a/frontends/p4/typeChecking/typeInference.cpp +++ b/frontends/p4/typeChecking/typeInference.cpp @@ -130,6 +130,9 @@ DEFINE_POSTORDER(IR::ReturnStatement) DEFINE_POSTORDER(IR::IfStatement) DEFINE_POSTORDER(IR::SwitchStatement) DEFINE_POSTORDER(IR::AssignmentStatement) +DEFINE_POSTORDER(IR::OpAssignmentStatement) +DEFINE_POSTORDER(IR::ShlAssign) +DEFINE_POSTORDER(IR::ShrAssign) DEFINE_POSTORDER(IR::ForInStatement) DEFINE_POSTORDER(IR::ActionListElement) DEFINE_POSTORDER(IR::KeyElement) diff --git a/frontends/parsers/p4/p4lexer.ll b/frontends/parsers/p4/p4lexer.ll index 89acaca2d85..d434607e299 100644 --- a/frontends/parsers/p4/p4lexer.ll +++ b/frontends/parsers/p4/p4lexer.ll @@ -283,6 +283,19 @@ using Parser = P4::P4Parser; "<=" { BEGIN(driver.saveState); driver.template_args = false; return makeToken(LE); } "++" { BEGIN(driver.saveState); driver.template_args = false; return makeToken(PP); } +"+=" { BEGIN(driver.saveState); driver.template_args = false; return makeToken(ASSIGN_PLUS); } +"-=" { BEGIN(driver.saveState); driver.template_args = false; return makeToken(ASSIGN_MINUS); } +"*=" { BEGIN(driver.saveState); driver.template_args = false; return makeToken(ASSIGN_MUL); } +"/=" { BEGIN(driver.saveState); driver.template_args = false; return makeToken(ASSIGN_DIV); } +"%=" { BEGIN(driver.saveState); driver.template_args = false; return makeToken(ASSIGN_MOD); } +"&=" { BEGIN(driver.saveState); driver.template_args = false; return makeToken(ASSIGN_BIT_AND); } +"|=" { BEGIN(driver.saveState); driver.template_args = false; return makeToken(ASSIGN_BIT_OR); } +"^=" { BEGIN(driver.saveState); driver.template_args = false; return makeToken(ASSIGN_XOR); } +"<<=" { BEGIN(driver.saveState); driver.template_args = false; return makeToken(ASSIGN_SHL); } +">>=" { BEGIN(driver.saveState); driver.template_args = false; return makeToken(ASSIGN_SHR); } +"|+|=" { BEGIN(driver.saveState); driver.template_args = false; return makeToken(ASSIGN_PLUS_SAT); } +"|-|=" { BEGIN(driver.saveState); driver.template_args = false; return makeToken(ASSIGN_MINUS_SAT); } + "+" { BEGIN(driver.saveState); driver.template_args = false; return makeToken(PLUS); } "{#}" { BEGIN(driver.saveState); driver.template_args = false; return makeToken(INVALID); } "|+|" { BEGIN(driver.saveState); driver.template_args = false; return makeToken(PLUS_SAT); } diff --git a/frontends/parsers/p4/p4parser.ypp b/frontends/parsers/p4/p4parser.ypp index dd69f9b9d8a..029cfc9b328 100644 --- a/frontends/parsers/p4/p4parser.ypp +++ b/frontends/parsers/p4/p4parser.ypp @@ -231,6 +231,18 @@ using namespace P4; %token QUESTION "?" %token DOT "." %token ASSIGN "=" +%token ASSIGN_PLUS "+=" +%token ASSIGN_MINUS "-=" +%token ASSIGN_MUL "*=" +%token ASSIGN_DIV "/=" +%token ASSIGN_MOD "%=" +%token ASSIGN_BIT_AND "&=" +%token ASSIGN_BIT_OR "|=" +%token ASSIGN_XOR "^=" +%token ASSIGN_SHL "<<=" +%token ASSIGN_SHR ">>=" +%token ASSIGN_PLUS_SAT "|+|=" +%token ASSIGN_MINUS_SAT "|-|=" %token SEMICOLON ";" %token AT "@" %token PP "++" @@ -1270,6 +1282,32 @@ assignmentOrMethodCallStatementWithoutSemicolon | lvalue "=" expression { $$ = new IR::AssignmentStatement(@2, $1, $3); } + | lvalue "*=" expression + { $$ = new IR::MulAssign(@2, $1, $3); } + | lvalue "/=" expression + { $$ = new IR::DivAssign(@2, $1, $3); } + | lvalue "%=" expression + { $$ = new IR::ModAssign(@2, $1, $3); } + | lvalue "+=" expression + { $$ = new IR::AddAssign(@2, $1, $3); } + | lvalue "-=" expression + { $$ = new IR::SubAssign(@2, $1, $3); } + | lvalue "|+|=" expression + { $$ = new IR::AddSatAssign(@2, $1, $3); } + | lvalue "|-|=" expression + { $$ = new IR::SubSatAssign(@2, $1, $3); } + | lvalue "<<=" expression + { $$ = new IR::ShlAssign(@2, $1, $3); } + | lvalue ">>=" expression + { $$ = new IR::ShrAssign(@2, $1, $3); } + | lvalue R_ANGLE_SHIFT ">=" expression + { $$ = new IR::ShrAssign(@2+@3, $1, $4); } + | lvalue "&=" expression + { $$ = new IR::BAndAssign(@2, $1, $3); } + | lvalue "|=" expression + { $$ = new IR::BOrAssign(@2, $1, $3); } + | lvalue "^=" expression + { $$ = new IR::BXorAssign(@2, $1, $3); } ; emptyStatement diff --git a/ir/dbprint-stmt.cpp b/ir/dbprint-stmt.cpp index e4799e7b09e..abb5ca012db 100644 --- a/ir/dbprint-stmt.cpp +++ b/ir/dbprint-stmt.cpp @@ -41,6 +41,12 @@ void IR::AssignmentStatement::dbprint(std::ostream &out) const { if (!prec) out << ';'; } +void IR::OpAssignmentStatement::dbprint(std::ostream &out) const { + int prec = getprec(out); + out << Prec_Low << left << " " << getStringOp() << "= " << right << setprec(prec); + if (!prec) out << ';'; +} + void IR::IfStatement::dbprint(std::ostream &out) const { int prec = getprec(out); out << Prec_Low << "if (" << condition << ") {" << indent << setprec(0) << Log::endl << ifTrue; diff --git a/ir/expression.def b/ir/expression.def index b8010588ae3..9d3cef759d5 100644 --- a/ir/expression.def +++ b/ir/expression.def @@ -73,41 +73,104 @@ class Mul : Operation_Binary { stringOp = "*"; precedence = DBPrint::Prec_Mul; } +class MulAssign : OpAssignmentStatement { +#emit + typedef Mul BinOp; +#end + stringOp = "*"; +#nodbprint +} class Div : Operation_Binary { stringOp = "/"; precedence = DBPrint::Prec_Div; } +class DivAssign : OpAssignmentStatement { +#emit + typedef Div BinOp; +#end + stringOp = "/"; +#nodbprint +} class Mod : Operation_Binary { stringOp = "%"; precedence = DBPrint::Prec_Mod; } +class ModAssign : OpAssignmentStatement { +#emit + typedef Mod BinOp; +#end + stringOp = "%"; +#nodbprint +} class Add : Operation_Binary { stringOp = "+"; precedence = DBPrint::Prec_Add; } +class AddAssign : OpAssignmentStatement { +#emit + typedef Add BinOp; +#end + stringOp = "+"; +#nodbprint +} class Sub : Operation_Binary { stringOp = "-"; precedence = DBPrint::Prec_Sub; } +class SubAssign : OpAssignmentStatement { +#emit + typedef Sub BinOp; +#end + stringOp = "-"; +#nodbprint +} class AddSat : Operation_Binary { stringOp = "|+|"; precedence = DBPrint::Prec_AddSat; } +class AddSatAssign : OpAssignmentStatement { +#emit + typedef AddSat BinOp; +#end + stringOp = "|+|"; +#nodbprint +} class SubSat : Operation_Binary { stringOp = "|-|"; precedence = DBPrint::Prec_SubSat; } +class SubSatAssign : OpAssignmentStatement { +#emit + typedef SubSat BinOp; +#end + stringOp = "|-|"; +#nodbprint +} class Shl : Operation_Binary { stringOp = "<<"; precedence = DBPrint::Prec_Shl; inline Shl { if (type->is() && left) type = left->type; } } +class ShlAssign : OpAssignmentStatement { +#emit + typedef Shl BinOp; +#end + stringOp = "<<"; +#nodbprint +} class Shr : Operation_Binary { stringOp = ">>"; precedence = DBPrint::Prec_Shr; inline Shr { if (type->is() && left) type = left->type; } } +class ShrAssign : OpAssignmentStatement { +#emit + typedef Shr BinOp; +#end + stringOp = ">>"; +#nodbprint +} class Equ : Operation_Relation { stringOp = "=="; precedence = DBPrint::Prec_Equ; @@ -136,14 +199,35 @@ class BAnd : Operation_Binary { stringOp = "&"; precedence = DBPrint::Prec_BAnd; } +class BAndAssign : OpAssignmentStatement { +#emit + typedef BAnd BinOp; +#end + stringOp = "&"; +#nodbprint +} class BOr : Operation_Binary { stringOp = "|"; precedence = DBPrint::Prec_BOr; } +class BOrAssign : OpAssignmentStatement { +#emit + typedef BOr BinOp; +#end + stringOp = "|"; +#nodbprint +} class BXor : Operation_Binary { stringOp = "^"; precedence = DBPrint::Prec_BXor; } +class BXorAssign : OpAssignmentStatement { +#emit + typedef BXor BinOp; +#end + stringOp = "^"; +#nodbprint +} class LAnd : Operation_Binary { stringOp = "&&"; precedence = DBPrint::Prec_LAnd; diff --git a/ir/ir.def b/ir/ir.def index 7f995bcfb64..e59cbd369e0 100644 --- a/ir/ir.def +++ b/ir/ir.def @@ -468,12 +468,21 @@ class EmptyStatement : Statement { dbprint { out << ""; } } -class AssignmentStatement : Statement { +abstract BaseAssignmentStatement : Statement { Expression left; Expression right; +} + +class AssignmentStatement : BaseAssignmentStatement { toString{ return absl::StrCat(left, " = ", right); } } +abstract OpAssignmentStatement : BaseAssignmentStatement { + virtual cstring getStringOp() const = 0; + toString{ return absl::StrCat(left, " ", getStringOp(), "= ", right); } + dbprint; +} + class IfStatement : Statement { Expression condition; Statement ifTrue; diff --git a/ir/node.cpp b/ir/node.cpp index b10420529c7..bcd54a753bc 100644 --- a/ir/node.cpp +++ b/ir/node.cpp @@ -100,8 +100,7 @@ cstring IR::Node::prepareSourceInfoForJSON(Util::SourceInfo &si, unsigned *lineN if (!si.isValid()) { return nullptr; } - if (is()) { - auto assign = to(); + if (auto assign = to()) { si = (assign->left->srcInfo + si) + assign->right->srcInfo; } return si.toSourcePositionData(lineNumber, columnNumber); diff --git a/ir/write_context.cpp b/ir/write_context.cpp index 142e31fc453..c815a1733a8 100644 --- a/ir/write_context.cpp +++ b/ir/write_context.cpp @@ -37,7 +37,7 @@ bool P4WriteContext::isWrite(bool root_value) { if (!ctxt || !ctxt->node) return root_value; } if (auto *prim = ctxt->node->to()) return prim->isOutput(ctxt->child_index); - if (ctxt->node->is()) return ctxt->child_index == 0; + if (ctxt->node->is()) return ctxt->child_index == 0; if (ctxt->node->is()) { // MethodCallExpression(Vector) if (!ctxt->parent || !ctxt->parent->parent || !ctxt->parent->parent->node) return false; diff --git a/midend/actionSynthesis.cpp b/midend/actionSynthesis.cpp index bad1a658c41..fbdd567f72a 100644 --- a/midend/actionSynthesis.cpp +++ b/midend/actionSynthesis.cpp @@ -96,7 +96,7 @@ bool DoSynthesizeActions::mustMove(const IR::MethodCallStatement *statement) { return true; } -bool DoSynthesizeActions::mustMove(const IR::AssignmentStatement *assign) { +bool DoSynthesizeActions::mustMove(const IR::BaseAssignmentStatement *assign) { if (auto mc = assign->right->to()) { auto mi = MethodInstance::resolve(mc, refMap, typeMap); if (!mi->is()) return true; @@ -123,7 +123,7 @@ const IR::Node *DoSynthesizeActions::preorder(IR::BlockStatement *statement) { for (auto c : statement->components) { bool moveToAction = false; - if (auto *as = c->to()) { + if (auto *as = c->to()) { moveToAction = mustMove(as); } else if (auto *mc = c->to()) { moveToAction = mustMove(mc); @@ -205,7 +205,7 @@ const IR::Statement *DoSynthesizeActions::createAction(const IR::Statement *toAd return result; } -const IR::Node *DoSynthesizeActions::preorder(IR::AssignmentStatement *statement) { +const IR::Node *DoSynthesizeActions::preorder(IR::BaseAssignmentStatement *statement) { // don't move stuff from for init/update -- should be part of policy? auto *ctxt = getContext(); if (ctxt->node->is() && diff --git a/midend/actionSynthesis.h b/midend/actionSynthesis.h index 4e7cfa173fb..0242bd4506d 100644 --- a/midend/actionSynthesis.h +++ b/midend/actionSynthesis.h @@ -128,7 +128,7 @@ class DoSynthesizeActions : public Transform { public: // If true the statement must be moved to an action bool mustMove(const IR::MethodCallStatement *statement); - bool mustMove(const IR::AssignmentStatement *statement); + bool mustMove(const IR::BaseAssignmentStatement *statement); DoSynthesizeActions(ReferenceMap *refMap, TypeMap *typeMap, ActionSynthesisPolicy *policy) : refMap(refMap), typeMap(typeMap), policy(policy) { @@ -148,7 +148,7 @@ class DoSynthesizeActions : public Transform { } // skip actions // We do not handle return: this pass should be called after it has been removed const IR::Node *preorder(IR::BlockStatement *statement) override; - const IR::Node *preorder(IR::AssignmentStatement *statement) override; + const IR::Node *preorder(IR::BaseAssignmentStatement *statement) override; const IR::Node *preorder(IR::MethodCallStatement *statement) override; const IR::Node *preorder(IR::ExitStatement *statement) override; const IR::Node *preorder(IR::Function *function) override { diff --git a/midend/coverage.cpp b/midend/coverage.cpp index ccc204466ef..d193ae3681a 100644 --- a/midend/coverage.cpp +++ b/midend/coverage.cpp @@ -16,7 +16,7 @@ bool SourceIdCmp::operator()(const IR::Node *s1, const IR::Node *s2) const { CollectNodes::CollectNodes(CoverageOptions coverageOptions) : coverageOptions(coverageOptions) {} -bool CollectNodes::preorder(const IR::AssignmentStatement *stmt) { +bool CollectNodes::preorder(const IR::BaseAssignmentStatement *stmt) { // Only track statements, which have a valid source position in the P4 program. if (coverageOptions.coverStatements && stmt->getSourceInfo().isValid()) { coverableNodes.insert(stmt); diff --git a/midend/coverage.h b/midend/coverage.h index aa0a1081912..bc707002be9 100644 --- a/midend/coverage.h +++ b/midend/coverage.h @@ -48,7 +48,7 @@ class CollectNodes : public Inspector { CoverageOptions coverageOptions; /// Statement coverage. - bool preorder(const IR::AssignmentStatement *stmt) override; + bool preorder(const IR::BaseAssignmentStatement *stmt) override; bool preorder(const IR::MethodCallStatement *stmt) override; bool preorder(const IR::ExitStatement *stmt) override; diff --git a/midend/def_use.cpp b/midend/def_use.cpp index f6124c73dbc..59ea6497c1d 100644 --- a/midend/def_use.cpp +++ b/midend/def_use.cpp @@ -415,7 +415,7 @@ bool ComputeDefUse::preorder(const IR::KeyElement *ke) { return false; } -bool ComputeDefUse::preorder(const IR::AssignmentStatement *as) { +bool ComputeDefUse::preorder(const IR::BaseAssignmentStatement *as) { // visit RHS of assignment before LHS visit(as->right, "right", 1); visit(as->left, "left", 0); diff --git a/midend/def_use.h b/midend/def_use.h index c58416c1630..0fb1efe2de4 100644 --- a/midend/def_use.h +++ b/midend/def_use.h @@ -169,7 +169,7 @@ class ComputeDefUse : public Inspector, bool preorder(const IR::Type *) override { return false; } bool preorder(const IR::Vector *) override { return false; } bool preorder(const IR::KeyElement *) override; - bool preorder(const IR::AssignmentStatement *) override; + bool preorder(const IR::BaseAssignmentStatement *) override; const IR::Expression *do_read(def_info_t &, const IR::Expression *, const Context *); const IR::Expression *do_write(def_info_t &, const IR::Expression *, const Context *); bool preorder(const IR::PathExpression *) override; diff --git a/midend/global_copyprop.cpp b/midend/global_copyprop.cpp index 3644ca53549..420433ac0f2 100644 --- a/midend/global_copyprop.cpp +++ b/midend/global_copyprop.cpp @@ -172,6 +172,18 @@ bool FindVariableValues::preorder(const IR::AssignmentStatement *stat) { return false; } +// Update the value for the 'stat->left' variable. +bool FindVariableValues::preorder(const IR::OpAssignmentStatement *stat) { + if (!working || GlobalCopyProp::lValueName(stat->left).isNullOrEmpty()) return false; + + LOG5("Working on statement: " << stat); + // Remove old values + if (vars[GlobalCopyProp::lValueName(stat->left)] == nullptr || + !(stat->right->equiv(*(vars[GlobalCopyProp::lValueName(stat->left)])))) + removeVarsContaining(&vars, GlobalCopyProp::lValueName(stat->left)); + return false; +} + // The core idea of this pass is represented here, it works on 'ActionCall' nodes by accessing // the body of the action using the pointer acquired by resolving the 'MethodCallExpression'. // An entry in the 'actions' map is set for the action node that was acquired by resolving @@ -312,7 +324,7 @@ class RemoveModifiedValues : public Inspector { TypeMap *typeMap; std::map *vars; - bool preorder(const IR::AssignmentStatement *stat) override { + bool preorder(const IR::BaseAssignmentStatement *stat) override { if ((*vars)[GlobalCopyProp::lValueName(stat->left)] == nullptr || !(stat->right->equiv(*((*vars)[GlobalCopyProp::lValueName(stat->left)])))) removeVarsContaining(vars, GlobalCopyProp::lValueName(stat->left)); @@ -363,7 +375,7 @@ IR::ForInStatement *DoGlobalCopyPropagation::preorder(IR::ForInStatement *stat) // Propagate values for variables on the right side of the statement // and update value for 'stat->left' variable if needed. -const IR::Node *DoGlobalCopyPropagation::preorder(IR::AssignmentStatement *stat) { +IR::BaseAssignmentStatement *DoGlobalCopyPropagation::preorder(IR::BaseAssignmentStatement *stat) { if (!performRewrite) return stat; LOG5("Working on statement: " << stat); @@ -378,6 +390,13 @@ const IR::Node *DoGlobalCopyPropagation::preorder(IR::AssignmentStatement *stat) visit(stat->left); performRewrite = true; prune(); + return stat; +} + +IR::Statement *DoGlobalCopyPropagation::preorder(IR::AssignmentStatement *assgn) { + if (!performRewrite) return assgn; + auto *stat = preorder(static_cast(assgn)); + // Store the value for 'stat->left' if it is now a constant. If it is an assignment to an // identical literal as already stored in the 'vars' container the statement is removed. if (auto lit = stat->right->to()) { diff --git a/midend/global_copyprop.h b/midend/global_copyprop.h index 7b80edf259d..83cecb97191 100644 --- a/midend/global_copyprop.h +++ b/midend/global_copyprop.h @@ -85,6 +85,7 @@ class FindVariableValues final : public Inspector { bool preorder(const IR::P4Table *) override; bool preorder(const IR::P4Action *) override; bool preorder(const IR::AssignmentStatement *) override; + bool preorder(const IR::OpAssignmentStatement *) override; void postorder(const IR::MethodCallExpression *) override; public: @@ -130,10 +131,11 @@ class DoGlobalCopyPropagation final : public Transform { IR::IfStatement *preorder(IR::IfStatement *) override; IR::ForStatement *preorder(IR::ForStatement *) override; IR::ForInStatement *preorder(IR::ForInStatement *) override; + IR::BaseAssignmentStatement *preorder(IR::BaseAssignmentStatement *) override; + IR::Statement *preorder(IR::AssignmentStatement *) override; const IR::Expression *postorder(IR::PathExpression *) override; const IR::Expression *preorder(IR::ArrayIndex *) override; const IR::Expression *preorder(IR::Member *) override; - const IR::Node *preorder(IR::AssignmentStatement *) override; const IR::P4Action *preorder(IR::P4Action *) override; const IR::P4Action *postorder(IR::P4Action *) override; IR::MethodCallExpression *postorder(IR::MethodCallExpression *) override; diff --git a/midend/has_side_effects.h b/midend/has_side_effects.h index 93c592cd347..82dca06ac54 100644 --- a/midend/has_side_effects.h +++ b/midend/has_side_effects.h @@ -29,7 +29,7 @@ class hasSideEffects : public Inspector, public ResolutionContext { P4::TypeMap *typeMap = nullptr; bool result = false; - bool preorder(const IR::AssignmentStatement *) override { + bool preorder(const IR::BaseAssignmentStatement *) override { result = true; return false; } diff --git a/midend/hsIndexSimplify.cpp b/midend/hsIndexSimplify.cpp index 60679b0cf42..2068047374f 100644 --- a/midend/hsIndexSimplify.cpp +++ b/midend/hsIndexSimplify.cpp @@ -110,8 +110,17 @@ IR::Node *HSIndexContretizer::eliminateArrayIndexes(HSIndexFinder &aiFinder, } else { pathExpr = generatedVariables->at(typeString); } - auto *newStatement = - new IR::AssignmentStatement(aiFinder.arrayIndex->srcInfo, expr, pathExpr); + IR::BaseAssignmentStatement *newStatement; + if (auto *oldAssign = statement->to()) { + newStatement = oldAssign->clone(); + newStatement->srcInfo = aiFinder.arrayIndex->srcInfo; + newStatement->left = expr; + newStatement->right = pathExpr; + } else { + newStatement = + new IR::AssignmentStatement(aiFinder.arrayIndex->srcInfo, expr, pathExpr); + } + auto *newCondition = new IR::Geq( aiFinder.newVariable, new IR::Constant(aiFinder.arrayIndex->right->type, sz - 1)); newIf = new IR::IfStatement(newCondition, newStatement, nullptr); @@ -122,7 +131,7 @@ IR::Node *HSIndexContretizer::eliminateArrayIndexes(HSIndexFinder &aiFinder, return new IR::BlockStatement(newComponents); } -IR::Node *HSIndexContretizer::preorder(IR::AssignmentStatement *assignmentStatement) { +IR::Node *HSIndexContretizer::preorder(IR::BaseAssignmentStatement *assignmentStatement) { HSIndexFinder aiFinder(locals, nameGen, typeMap, generatedVariables); assignmentStatement->left->apply(aiFinder); if (aiFinder.arrayIndex == nullptr) { diff --git a/midend/hsIndexSimplify.h b/midend/hsIndexSimplify.h index 55bd338cc5d..e5a69ba22ac 100644 --- a/midend/hsIndexSimplify.h +++ b/midend/hsIndexSimplify.h @@ -95,7 +95,7 @@ class HSIndexContretizer : public Transform { } IR::Node *preorder(IR::IfStatement *ifStatement) override; - IR::Node *preorder(IR::AssignmentStatement *assignmentStatement) override; + IR::Node *preorder(IR::BaseAssignmentStatement *assignmentStatement) override; IR::Node *preorder(IR::BlockStatement *blockStatement) override; IR::Node *preorder(IR::MethodCallStatement *methodCallStatement) override; IR::Node *preorder(IR::P4Control *control) override; diff --git a/midend/local_copyprop.cpp b/midend/local_copyprop.cpp index bb9c309a450..c26ff03f4f7 100644 --- a/midend/local_copyprop.cpp +++ b/midend/local_copyprop.cpp @@ -80,7 +80,7 @@ class DoLocalCopyPropagation::ElimDead : public Transform { } return var; } - const IR::Statement *postorder(IR::AssignmentStatement *as) override { + const IR::Statement *postorder(IR::BaseAssignmentStatement *as) override { if (auto dest = lvalue_out(as->left)->to()) { if (auto var = ::P4::getref(self.available, dest->path->name)) { if (var->local && !var->live) { @@ -389,7 +389,7 @@ IR::Statement *DoLocalCopyPropagation::preorder(IR::Statement *s) { return s; } -IR::AssignmentStatement *DoLocalCopyPropagation::preorder(IR::AssignmentStatement *as) { +const IR::Node *DoLocalCopyPropagation::preorder(IR::BaseAssignmentStatement *as) { visitAgain(); if (!working) return as; // visit the source subtree first, before the destination subtree @@ -402,7 +402,7 @@ IR::AssignmentStatement *DoLocalCopyPropagation::preorder(IR::AssignmentStatemen visit(as->right, "right", 1); visit(as->left, "left", 0); prune(); - return postorder(as); + return as->apply_visitor_postorder(*this); } IR::AssignmentStatement *DoLocalCopyPropagation::postorder(IR::AssignmentStatement *as) { @@ -443,7 +443,7 @@ IR::AssignmentStatement *DoLocalCopyPropagation::postorder(IR::AssignmentStateme return as; } -void DoLocalCopyPropagation::LoopPrepass::postorder(const IR::AssignmentStatement *as) { +void DoLocalCopyPropagation::LoopPrepass::postorder(const IR::BaseAssignmentStatement *as) { if (auto dest = expr_name(as->left)) self.dropValuesUsing(dest); } diff --git a/midend/local_copyprop.h b/midend/local_copyprop.h index 1c2d6abb1c6..b1fdfada2b2 100644 --- a/midend/local_copyprop.h +++ b/midend/local_copyprop.h @@ -108,7 +108,7 @@ class DoLocalCopyPropagation : public ControlFlowVisitor, class LoopPrepass : public Inspector { DoLocalCopyPropagation &self; - void postorder(const IR::AssignmentStatement *) override; + void postorder(const IR::BaseAssignmentStatement *) override; void postorder(const IR::MethodCallExpression *) override; void apply_table(TableInfo *tbl); void apply_function(FuncInfo *tbl); @@ -125,7 +125,7 @@ class DoLocalCopyPropagation : public ControlFlowVisitor, const IR::Expression *preorder(IR::Member *) override; const IR::Expression *preorder(IR::ArrayIndex *) override; IR::Statement *preorder(IR::Statement *) override; - IR::AssignmentStatement *preorder(IR::AssignmentStatement *) override; + const IR::Node *preorder(IR::BaseAssignmentStatement *) override; IR::AssignmentStatement *postorder(IR::AssignmentStatement *) override; IR::IfStatement *postorder(IR::IfStatement *) override; IR::ForStatement *preorder(IR::ForStatement *) override; diff --git a/midend/predication.cpp b/midend/predication.cpp index f536ec641e4..30f37d9343d 100644 --- a/midend/predication.cpp +++ b/midend/predication.cpp @@ -145,13 +145,13 @@ const IR::Expression *Predication::clone(const IR::Expression *expression) { return expression->apply(cloner); } -const IR::Node *Predication::clone(const IR::AssignmentStatement *statement) { +const IR::AssignmentStatement *Predication::clone(const IR::AssignmentStatement *statement) { // Expressions often need to be cloned. This is necessary because // in the end different code will be generated for the different clones of // an expression. CloneExpressions cloner; cloner.setCalledBy(this); - return statement->apply(cloner); + return statement->apply(cloner)->to(); } /// expressionReplacer is applied here and the assignment is stored in liveAssigns vector @@ -172,8 +172,7 @@ const IR::Node *Predication::preorder(IR::AssignmentStatement *statement) { modifyIndex = false; } // The expressionReplacer responsible for transforming this statement - ExpressionReplacer replacer(clone(statement)->to(), traversalPath, - conditions); + ExpressionReplacer replacer(clone(statement), traversalPath, conditions); replacer.setCalledBy(this); dependencies.clear(); visit(statement->right); @@ -237,6 +236,17 @@ const IR::Node *Predication::preorder(IR::AssignmentStatement *statement) { return new IR::EmptyStatement(); } +const IR::Node *Predication::preorder(IR::OpAssignmentStatement *statement) { + if (!isInContext() || ifNestingLevel == 0) { + return statement; + } + ::P4::error( + ErrorType::ERR_EXPRESSION, + "%1%: Op-Assignment inside if statement can't be transformed to condition expression", + statement); + return statement; +} + const IR::Node *Predication::preorder(IR::PathExpression *pathExpr) { dependencies.push_back(Pred::lvalueName(pathExpr)); return pathExpr; @@ -262,8 +272,7 @@ const IR::Node *Predication::preorder(IR::ArrayIndex *arrInd) { auto indexDecl = new IR::Declaration_Variable(indexName, arrInd->right->type->getP4Type()); auto index = new IR::PathExpression(IR::ID(indexName)); auto indexAssignment = new IR::AssignmentStatement(index, clone(arrInd->right)); - ExpressionReplacer replacer(clone(indexAssignment)->to(), - traversalPath, conditions); + ExpressionReplacer replacer(clone(indexAssignment), traversalPath, conditions); // Creates the initial Mux expression replacer.setVisitingIndex(true); indexAssignment->right = diff --git a/midend/predication.h b/midend/predication.h index 5c348465714..3c399549b5e 100644 --- a/midend/predication.h +++ b/midend/predication.h @@ -57,7 +57,7 @@ class Predication final : public Transform { class ExpressionReplacer final : public Transform { private: // Original assignment that the replacer works on - const IR::AssignmentStatement *statement; + const IR::AssignmentStatement *const statement; // To keep track of the path used while traversing nested if-else statements: // IF - true / ELSE - false const std::vector &traversalPath; @@ -133,11 +133,12 @@ class Predication final : public Transform { } const IR::Expression *clone(const IR::Expression *expression); - const IR::Node *clone(const IR::AssignmentStatement *statement); + const IR::AssignmentStatement *clone(const IR::AssignmentStatement *statement); const IR::Node *preorder(IR::IfStatement *statement) override; const IR::Node *preorder(IR::P4Action *action) override; const IR::Node *postorder(IR::P4Action *action) override; const IR::Node *preorder(IR::AssignmentStatement *statement) override; + const IR::Node *preorder(IR::OpAssignmentStatement *statement) override; // Assignment dependecy checkers const IR::Node *preorder(IR::PathExpression *pathExpr) override; const IR::Node *preorder(IR::Member *member) override; diff --git a/midend/removeExits.cpp b/midend/removeExits.cpp index 427175a0313..d1de2501405 100644 --- a/midend/removeExits.cpp +++ b/midend/removeExits.cpp @@ -236,7 +236,7 @@ const IR::Node *DoRemoveExits::preorder(IR::SwitchStatement *statement) { return statement; } -const IR::Node *DoRemoveExits::preorder(IR::AssignmentStatement *statement) { +const IR::Node *DoRemoveExits::preorder(IR::BaseAssignmentStatement *statement) { CallsExit ce(this, typeMap, &callsExit); ce.setCalledBy(this); (void)statement->apply(ce, getChildContext()); diff --git a/midend/removeExits.h b/midend/removeExits.h index 6c62d2af4f5..c1ad3b4375c 100644 --- a/midend/removeExits.h +++ b/midend/removeExits.h @@ -51,7 +51,7 @@ class DoRemoveExits : public DoRemoveReturns, public ResolutionContext { const IR::Node *preorder(IR::BlockStatement *statement) override; const IR::Node *preorder(IR::IfStatement *statement) override; const IR::Node *preorder(IR::SwitchStatement *statement) override; - const IR::Node *preorder(IR::AssignmentStatement *statement) override; + const IR::Node *preorder(IR::BaseAssignmentStatement *statement) override; const IR::Node *preorder(IR::MethodCallStatement *statement) override; const IR::Node *preorder(IR::P4Action *action) override; diff --git a/midend/simplifyBitwise.cpp b/midend/simplifyBitwise.cpp index 7e53d003155..947129699d4 100644 --- a/midend/simplifyBitwise.cpp +++ b/midend/simplifyBitwise.cpp @@ -12,13 +12,15 @@ void SimplifyBitwise::assignSlices(const IR::Expression *expr, big_int mask) { int zero_pos = Util::scan0(mask, one_pos); auto left_slice = IR::Slice::make(changing_as->left, one_pos, zero_pos - 1); auto right_slice = IR::Slice::make(expr, one_pos, zero_pos - 1); - auto new_as = new IR::AssignmentStatement(changing_as->srcInfo, left_slice, right_slice); + auto new_as = changing_as->clone(); + new_as->left = left_slice; + new_as->right = right_slice; slice_statements->push_back(new_as); one_pos = Util::scan1(mask, zero_pos); } } -const IR::Node *SimplifyBitwise::preorder(IR::AssignmentStatement *as) { +const IR::Node *SimplifyBitwise::preorder(IR::BaseAssignmentStatement *as) { Pattern::Match a, b; Pattern::Match maskA, maskB; diff --git a/midend/simplifyBitwise.h b/midend/simplifyBitwise.h index b732f4beb50..54868e04112 100644 --- a/midend/simplifyBitwise.h +++ b/midend/simplifyBitwise.h @@ -15,22 +15,66 @@ namespace P4 { * This gets translated to the following p4_16: * hdr.field = hdr.field & ~mask | parameter & mask; * - * which in term could be further simplified to a vector of simple assignments over slices. + * which in term can be further simplified to a vector of simple assignments over slices. * This extensions could be folded to any combinations of Binary Ors and Binary Ands as long * as the masks never have any collisions. * + * Currently we deal with any assignment of the form + * + * dest = (srcA & maskA) | (srcB & maskB); + * + * where `maskA' and `maskB' are constants such that maskA & maskB == 0. + * This gets converted into + * + * dest[slice_A1] = srcA[slice_A1] + * : + * dest[slice_An] = srcA[slice_An] + * dest[slice_B1] = srcA[slice_B1] + * : + * dest[slice_Bn] = srcA[slice_Bn] + * dest[slice_X1] = 0 + * : + * + * where the slice_Ai/Bi values are slices corresponding to each range of contiguous 1 bits + * in maskA and maskB, and the slice_Xi values are any remaing bits where both masks are 0. + * For example if maskA == 0xff00ff and maskB = 0xff00 they will be: + * + * slice_A1 == 7:0 + * slice_A2 == 23:16 + * slice_B1 == 15:8 + * slice_X1 == 31:24 (assuming bit<32> types involved) + * + * Naturally, if there are no uncovered bits, there will be no X slices. The most common + * case will end up with one A slice and one B slice and no X slice, but if the masks are + * sparse/pessimal this will generate a lot of small slices which may be worse than the + * original code, so perhaps there should be a knob targets can use to limit that. + * + * This works equally well for `&=', `|=' and `^=' as it does for simple assignments. + * Any resulting `dest[slice] |= 0' or `des[slice] ^= 0' should later be eliminated by + * constant folding. + * * @pre none * * @todo: Extend the optimization to handle multiple combinations of masks */ class SimplifyBitwise : public Transform { IR::Vector *slice_statements = nullptr; - const IR::AssignmentStatement *changing_as = nullptr; + const IR::BaseAssignmentStatement *changing_as = nullptr; void assignSlices(const IR::Expression *expr, big_int mask); public: - const IR::Node *preorder(IR::AssignmentStatement *as) override; + const IR::Node *preorder(IR::BaseAssignmentStatement *as) override; + const IR::Node *preorder(IR::OpAssignmentStatement *as) override { return as; } + const IR::Node *preorder(IR::BAndAssign *as) override { + return preorder(static_cast(as)); + } + const IR::Node *preorder(IR::BOrAssign *as) override { + return preorder(static_cast(as)); + } + const IR::Node *preorder(IR::BXorAssign *as) override { + return preorder(static_cast(as)); + } }; } // namespace P4 diff --git a/midend/simplifyKey.h b/midend/simplifyKey.h index 1dd7a238195..a7ae8c8cc2b 100644 --- a/midend/simplifyKey.h +++ b/midend/simplifyKey.h @@ -173,7 +173,7 @@ class DoSimplifyKey : public Transform { const IR::Node *postorder(IR::SwitchStatement *statement) override { return doStatement(statement, statement->expression); } - const IR::Node *postorder(IR::AssignmentStatement *statement) override { + const IR::Node *postorder(IR::BaseAssignmentStatement *statement) override { return doStatement(statement, statement->right); } const IR::Node *postorder(IR::KeyElement *element) override; diff --git a/midend/tableHit.cpp b/midend/tableHit.cpp index 173c5453f60..0fef4f97231 100644 --- a/midend/tableHit.cpp +++ b/midend/tableHit.cpp @@ -20,7 +20,7 @@ limitations under the License. namespace P4 { -const IR::Node *DoTableHit::postorder(IR::AssignmentStatement *statement) { +const IR::Node *DoTableHit::process(IR::BaseAssignmentStatement *statement, DoTableHit::op_t op) { LOG3("Visiting " << getOriginal()); auto right = statement->right; bool negated = false; @@ -32,8 +32,28 @@ const IR::Node *DoTableHit::postorder(IR::AssignmentStatement *statement) { if (!TableApplySolver::isHit(right, this, typeMap)) return statement; - auto tstat = new IR::AssignmentStatement(statement->left->clone(), new IR::BoolLiteral(true)); - auto fstat = new IR::AssignmentStatement(statement->left->clone(), new IR::BoolLiteral(false)); + const IR::Statement *tstat, *fstat; + switch (op) { + case None: + tstat = + new IR::AssignmentStatement(statement->left->clone(), new IR::BoolLiteral(true)); + fstat = new IR::AssignmentStatement(statement->left, new IR::BoolLiteral(false)); + break; + case And: + tstat = new IR::EmptyStatement; + fstat = new IR::AssignmentStatement(statement->left, new IR::BoolLiteral(false)); + break; + case Or: + tstat = new IR::AssignmentStatement(statement->left, new IR::BoolLiteral(true)); + fstat = new IR::EmptyStatement; + break; + case Xor: + tstat = new IR::BXorAssign(statement->left, new IR::BoolLiteral(true)); + fstat = new IR::EmptyStatement; + break; + default: + BUG("invalid op_t in DoTableHit"); + } if (negated) return new IR::IfStatement(right, fstat, tstat); else diff --git a/midend/tableHit.h b/midend/tableHit.h index a74aa05685f..fe542d1585a 100644 --- a/midend/tableHit.h +++ b/midend/tableHit.h @@ -35,13 +35,27 @@ This may be needed by some back-ends which only support hit test in conditionals */ class DoTableHit : public Transform, public ResolutionContext { TypeMap *typeMap; + enum op_t { None, And, Or, Xor }; + + const IR::Node *process(IR::BaseAssignmentStatement *statement, op_t op); public: + const IR::Node *postorder(IR::BaseAssignmentStatement *statement) override { + return process(statement, None); + } + const IR::Node *postorder(IR::OpAssignmentStatement *statement) override { return statement; } + const IR::Node *postorder(IR::BAndAssign *statement) override { + return process(statement, And); + } + const IR::Node *postorder(IR::BOrAssign *statement) override { return process(statement, Or); } + const IR::Node *postorder(IR::BXorAssign *statement) override { + return process(statement, Xor); + } + explicit DoTableHit(TypeMap *typeMap) : typeMap(typeMap) { CHECK_NULL(typeMap); setName("DoTableHit"); } - const IR::Node *postorder(IR::AssignmentStatement *statement) override; }; class TableHit : public PassManager { diff --git a/midend/unrollLoops.cpp b/midend/unrollLoops.cpp index e9e15b2dca1..f7288d8a816 100644 --- a/midend/unrollLoops.cpp +++ b/midend/unrollLoops.cpp @@ -117,7 +117,7 @@ class ReplaceIndexRefs : public Transform, P4WriteContext { } return new IR::Constant(pe->type, value); } - IR::AssignmentStatement *preorder(IR::AssignmentStatement *assign) { + IR::BaseAssignmentStatement *preorder(IR::BaseAssignmentStatement *assign) { visit(assign->right, "right", 1); visit(assign->left, "left", 0); prune(); @@ -180,6 +180,20 @@ long UnrollLoops::evalLoop(const IR::Expression *exp, long val, return val; } +long UnrollLoops::evalLoop(const IR::BaseAssignmentStatement *assign, long val, + const ComputeDefUse::locset_t &idefs, bool &fail) { + if (assign->is()) return evalLoop(assign->right, val, idefs, fail); + if (assign->is()) return val * evalLoop(assign->right, val, idefs, fail); + if (assign->is()) return val / evalLoop(assign->right, val, idefs, fail); + if (assign->is()) return val % evalLoop(assign->right, val, idefs, fail); + if (assign->is()) return val + evalLoop(assign->right, val, idefs, fail); + if (assign->is()) return val - evalLoop(assign->right, val, idefs, fail); + if (assign->is()) return val << evalLoop(assign->right, val, idefs, fail); + if (assign->is()) return val >> evalLoop(assign->right, val, idefs, fail); + fail = true; + return 1; +} + bool UnrollLoops::findLoopBounds(IR::ForStatement *fstmt, loop_bounds_t &bounds) { Pattern::Match v; Pattern::Match k; @@ -187,7 +201,7 @@ bool UnrollLoops::findLoopBounds(IR::ForStatement *fstmt, loop_bounds_t &bounds) auto d = resolveUnique(v->path->name, P4::ResolutionType::Any); bounds.index = d ? d->to() : nullptr; if (!bounds.index) return false; - const IR::AssignmentStatement *init = nullptr, *incr = nullptr; + const IR::BaseAssignmentStatement *init = nullptr, *incr = nullptr; const IR::Constant *initval = nullptr; auto &index_defs = defUse->getDefs(v); if (index_defs.size() != 2) { @@ -196,10 +210,10 @@ bool UnrollLoops::findLoopBounds(IR::ForStatement *fstmt, loop_bounds_t &bounds) return false; } else if ((init = index_defs.front()->parent->node->to()) && (initval = init->right->to())) { - incr = index_defs.back()->parent->node->to(); + incr = index_defs.back()->parent->node->to(); } else if ((init = index_defs.back()->parent->node->to()) && (initval = init->right->to())) { - incr = index_defs.front()->parent->node->to(); + incr = index_defs.front()->parent->node->to(); } else { return false; } @@ -207,7 +221,7 @@ bool UnrollLoops::findLoopBounds(IR::ForStatement *fstmt, loop_bounds_t &bounds) bool fail = false; for (long val = initval->asLong(); evalLoop(fstmt->condition, val, index_defs, fail) && !fail; - val = evalLoop(incr->right, val, index_defs, fail)) { + val = evalLoop(incr, val, index_defs, fail)) { if (bounds.indexes.size() > 1000) { fail = true; break; diff --git a/midend/unrollLoops.h b/midend/unrollLoops.h index 3ea7cdc2d44..796a024b3be 100644 --- a/midend/unrollLoops.h +++ b/midend/unrollLoops.h @@ -40,6 +40,8 @@ class UnrollLoops : public Transform, public P4::ResolutionContext { private: long evalLoop(const IR::Expression *, long, const ComputeDefUse::locset_t &, bool &); + long evalLoop(const IR::BaseAssignmentStatement *, long, const ComputeDefUse::locset_t &, + bool &); bool findLoopBounds(IR::ForStatement *, loop_bounds_t &); bool findLoopBounds(IR::ForInStatement *, loop_bounds_t &); const IR::Statement *doUnroll(const loop_bounds_t &, const IR::Statement *, diff --git a/testdata/p4_16_errors/compound-assignment.p4 b/testdata/p4_16_errors/compound-assignment.p4 new file mode 100644 index 00000000000..e1cc8b95c26 --- /dev/null +++ b/testdata/p4_16_errors/compound-assignment.p4 @@ -0,0 +1,163 @@ +/* +Copyright 2022-present University of Oxford + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include + +const bit<24> P4CALC_ADD = 0x2b; // '+' +const bit<24> P4CALC_SUB = 0x2d; // '-' +const bit<24> P4CALC_MUL = 0x2a; // '*' +const bit<24> P4CALC_DIV = 0x2f; // '/' +const bit<24> P4CALC_MOD = 0x25; // '%' +const bit<24> P4CALC_BAND = 0x26; // '&' +const bit<24> P4CALC_BOR = 0x7c; // '|' +const bit<24> P4CALC_BXOR = 0x5e; // '^' +const bit<24> P4CALC_SHL = 0x3c3c; // '<<' +const bit<24> P4CALC_SHR = 0x3e3e; // '>>' +const bit<24> P4CALC_SATADD = 0x7c2b7c; // '|+|' +const bit<24> P4CALC_SATSUB = 0x7c2d7c; // '|-|' + +header p4calc_t { + bit<24> op; + bit<32> operand_a; + bit<32> operand_b; + bit<32> res; +} + +struct headers { + p4calc_t p4calc; +} + +control caller(inout headers hdr) { + action operation_add() { + bit<32> result = hdr.p4calc.operand_a; + result += hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + + action operation_sub() { + bit<32> result = hdr.p4calc.operand_a; + result -= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + + action operation_mul() { + bit<32> result = hdr.p4calc.operand_a; + result *= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + + action operation_div() { + bit<32> result = hdr.p4calc.operand_a; + result /= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + + action operation_mod() { + bit<32> result = hdr.p4calc.operand_a; + result %= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + + action operation_band() { + bit<32> result = hdr.p4calc.operand_a; + result &= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + + action operation_bor() { + bit<32> result = hdr.p4calc.operand_a; + result |= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + + action operation_bxor() { + bit<32> result = hdr.p4calc.operand_a; + result ^= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + + action operation_shl() { + bit<32> result = hdr.p4calc.operand_a; + result <<= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + + action operation_shr() { + bit<32> result = hdr.p4calc.operand_a; + result >>= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + + action operation_addsat() { + bit<32> result = hdr.p4calc.operand_a; + result |+|= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + + action operation_subsat() { + bit<32> result = hdr.p4calc.operand_a; + result |-|= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + + table calculate { + key = { + hdr.p4calc.op : exact; + } + actions = { + operation_add; + operation_sub; + operation_mul; + operation_div; + operation_mod; + operation_band; + operation_bor; + operation_bxor; + operation_shl; + operation_shr; + operation_addsat; + operation_subsat; + NoAction; + } + const default_action = NoAction(); + const entries = { + P4CALC_ADD : operation_add(); + P4CALC_SUB : operation_sub(); + P4CALC_MUL : operation_mul(); + P4CALC_DIV : operation_div(); + P4CALC_MOD : operation_mod(); + P4CALC_BAND : operation_band(); + P4CALC_BOR : operation_bor(); + P4CALC_BXOR : operation_bxor(); + P4CALC_SHL : operation_shl(); + P4CALC_SHR : operation_shr(); + P4CALC_SATADD : operation_addsat(); + P4CALC_SATSUB : operation_subsat(); + } + } + + apply { + if (hdr.p4calc.isValid()) { + calculate.apply(); + } + } +} + +control Ingress(inout H hdr); + +package s(Ingress ig); + +s(caller()) main; \ No newline at end of file diff --git a/testdata/p4_16_errors_outputs/compound-assignment-first.p4 b/testdata/p4_16_errors_outputs/compound-assignment-first.p4 new file mode 100644 index 00000000000..ef8bc61e5c1 --- /dev/null +++ b/testdata/p4_16_errors_outputs/compound-assignment-first.p4 @@ -0,0 +1,131 @@ +#include + +const bit<24> P4CALC_ADD = 24w0x2b; +const bit<24> P4CALC_SUB = 24w0x2d; +const bit<24> P4CALC_MUL = 24w0x2a; +const bit<24> P4CALC_DIV = 24w0x2f; +const bit<24> P4CALC_MOD = 24w0x25; +const bit<24> P4CALC_BAND = 24w0x26; +const bit<24> P4CALC_BOR = 24w0x7c; +const bit<24> P4CALC_BXOR = 24w0x5e; +const bit<24> P4CALC_SHL = 24w0x3c3c; +const bit<24> P4CALC_SHR = 24w0x3e3e; +const bit<24> P4CALC_SATADD = 24w0x7c2b7c; +const bit<24> P4CALC_SATSUB = 24w0x7c2d7c; +header p4calc_t { + bit<24> op; + bit<32> operand_a; + bit<32> operand_b; + bit<32> res; +} + +struct headers { + p4calc_t p4calc; +} + +control caller(inout headers hdr) { + action operation_add() { + bit<32> result = hdr.p4calc.operand_a; + result += hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_sub() { + bit<32> result = hdr.p4calc.operand_a; + result -= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_mul() { + bit<32> result = hdr.p4calc.operand_a; + result *= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_div() { + bit<32> result = hdr.p4calc.operand_a; + result /= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_mod() { + bit<32> result = hdr.p4calc.operand_a; + result %= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_band() { + bit<32> result = hdr.p4calc.operand_a; + result &= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_bor() { + bit<32> result = hdr.p4calc.operand_a; + result |= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_bxor() { + bit<32> result = hdr.p4calc.operand_a; + result ^= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_shl() { + bit<32> result = hdr.p4calc.operand_a; + result <<= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_shr() { + bit<32> result = hdr.p4calc.operand_a; + result >>= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_addsat() { + bit<32> result = hdr.p4calc.operand_a; + result |+|= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_subsat() { + bit<32> result = hdr.p4calc.operand_a; + result |-|= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + table calculate { + key = { + hdr.p4calc.op: exact @name("hdr.p4calc.op"); + } + actions = { + operation_add(); + operation_sub(); + operation_mul(); + operation_div(); + operation_mod(); + operation_band(); + operation_bor(); + operation_bxor(); + operation_shl(); + operation_shr(); + operation_addsat(); + operation_subsat(); + NoAction(); + } + const default_action = NoAction(); + const entries = { + 24w0x2b : operation_add(); + 24w0x2d : operation_sub(); + 24w0x2a : operation_mul(); + 24w0x2f : operation_div(); + 24w0x25 : operation_mod(); + 24w0x26 : operation_band(); + 24w0x7c : operation_bor(); + 24w0x5e : operation_bxor(); + 24w0x3c3c : operation_shl(); + 24w0x3e3e : operation_shr(); + 24w0x7c2b7c : operation_addsat(); + 24w0x7c2d7c : operation_subsat(); + } + } + apply { + if (hdr.p4calc.isValid()) { + calculate.apply(); + } + } +} + +control Ingress(inout H hdr); +package s(Ingress ig); +s(caller()) main; diff --git a/testdata/p4_16_errors_outputs/compound-assignment-frontend.p4 b/testdata/p4_16_errors_outputs/compound-assignment-frontend.p4 new file mode 100644 index 00000000000..0220896c4e9 --- /dev/null +++ b/testdata/p4_16_errors_outputs/compound-assignment-frontend.p4 @@ -0,0 +1,133 @@ +#include + +header p4calc_t { + bit<24> op; + bit<32> operand_a; + bit<32> operand_b; + bit<32> res; +} + +struct headers { + p4calc_t p4calc; +} + +control caller(inout headers hdr) { + @name("caller.result") bit<32> result_0; + @name("caller.result") bit<32> result_1; + @name("caller.result") bit<32> result_2; + @name("caller.result") bit<32> result_3; + @name("caller.result") bit<32> result_4; + @name("caller.result") bit<32> result_5; + @name("caller.result") bit<32> result_6; + @name("caller.result") bit<32> result_7; + @name("caller.result") bit<32> result_8; + @name("caller.result") bit<32> result_9; + @name("caller.result") bit<32> result_10; + @name("caller.result") bit<32> result_11; + @noWarn("unused") @name(".NoAction") action NoAction_1() { + } + @name("caller.operation_add") action operation_add() { + result_0 = hdr.p4calc.operand_a; + result_0 = result_0 + hdr.p4calc.operand_b; + hdr.p4calc.res = result_0; + } + @name("caller.operation_sub") action operation_sub() { + result_1 = hdr.p4calc.operand_a; + result_1 = result_1 - hdr.p4calc.operand_b; + hdr.p4calc.res = result_1; + } + @name("caller.operation_mul") action operation_mul() { + result_2 = hdr.p4calc.operand_a; + result_2 = result_2 * hdr.p4calc.operand_b; + hdr.p4calc.res = result_2; + } + @name("caller.operation_div") action operation_div() { + result_3 = hdr.p4calc.operand_a; + result_3 = result_3 / hdr.p4calc.operand_b; + hdr.p4calc.res = result_3; + } + @name("caller.operation_mod") action operation_mod() { + result_4 = hdr.p4calc.operand_a; + result_4 = result_4 % hdr.p4calc.operand_b; + hdr.p4calc.res = result_4; + } + @name("caller.operation_band") action operation_band() { + result_5 = hdr.p4calc.operand_a; + result_5 = result_5 & hdr.p4calc.operand_b; + hdr.p4calc.res = result_5; + } + @name("caller.operation_bor") action operation_bor() { + result_6 = hdr.p4calc.operand_a; + result_6 = result_6 | hdr.p4calc.operand_b; + hdr.p4calc.res = result_6; + } + @name("caller.operation_bxor") action operation_bxor() { + result_7 = hdr.p4calc.operand_a; + result_7 = result_7 ^ hdr.p4calc.operand_b; + hdr.p4calc.res = result_7; + } + @name("caller.operation_shl") action operation_shl() { + result_8 = hdr.p4calc.operand_a; + result_8 = result_8 << hdr.p4calc.operand_b; + hdr.p4calc.res = result_8; + } + @name("caller.operation_shr") action operation_shr() { + result_9 = hdr.p4calc.operand_a; + result_9 = result_9 >> hdr.p4calc.operand_b; + hdr.p4calc.res = result_9; + } + @name("caller.operation_addsat") action operation_addsat() { + result_10 = hdr.p4calc.operand_a; + result_10 = result_10 |+| hdr.p4calc.operand_b; + hdr.p4calc.res = result_10; + } + @name("caller.operation_subsat") action operation_subsat() { + result_11 = hdr.p4calc.operand_a; + result_11 = result_11 |-| hdr.p4calc.operand_b; + hdr.p4calc.res = result_11; + } + @name("caller.calculate") table calculate_0 { + key = { + hdr.p4calc.op: exact @name("hdr.p4calc.op"); + } + actions = { + operation_add(); + operation_sub(); + operation_mul(); + operation_div(); + operation_mod(); + operation_band(); + operation_bor(); + operation_bxor(); + operation_shl(); + operation_shr(); + operation_addsat(); + operation_subsat(); + NoAction_1(); + } + const default_action = NoAction_1(); + const entries = { + 24w0x2b : operation_add(); + 24w0x2d : operation_sub(); + 24w0x2a : operation_mul(); + 24w0x2f : operation_div(); + 24w0x25 : operation_mod(); + 24w0x26 : operation_band(); + 24w0x7c : operation_bor(); + 24w0x5e : operation_bxor(); + 24w0x3c3c : operation_shl(); + 24w0x3e3e : operation_shr(); + 24w0x7c2b7c : operation_addsat(); + 24w0x7c2d7c : operation_subsat(); + } + } + apply { + if (hdr.p4calc.isValid()) { + calculate_0.apply(); + } + } +} + +control Ingress(inout H hdr); +package s(Ingress ig); +s(caller()) main; diff --git a/testdata/p4_16_errors_outputs/compound-assignment.p4 b/testdata/p4_16_errors_outputs/compound-assignment.p4 new file mode 100644 index 00000000000..4f2cf7cf7aa --- /dev/null +++ b/testdata/p4_16_errors_outputs/compound-assignment.p4 @@ -0,0 +1,131 @@ +#include + +const bit<24> P4CALC_ADD = 0x2b; +const bit<24> P4CALC_SUB = 0x2d; +const bit<24> P4CALC_MUL = 0x2a; +const bit<24> P4CALC_DIV = 0x2f; +const bit<24> P4CALC_MOD = 0x25; +const bit<24> P4CALC_BAND = 0x26; +const bit<24> P4CALC_BOR = 0x7c; +const bit<24> P4CALC_BXOR = 0x5e; +const bit<24> P4CALC_SHL = 0x3c3c; +const bit<24> P4CALC_SHR = 0x3e3e; +const bit<24> P4CALC_SATADD = 0x7c2b7c; +const bit<24> P4CALC_SATSUB = 0x7c2d7c; +header p4calc_t { + bit<24> op; + bit<32> operand_a; + bit<32> operand_b; + bit<32> res; +} + +struct headers { + p4calc_t p4calc; +} + +control caller(inout headers hdr) { + action operation_add() { + bit<32> result = hdr.p4calc.operand_a; + result += hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_sub() { + bit<32> result = hdr.p4calc.operand_a; + result -= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_mul() { + bit<32> result = hdr.p4calc.operand_a; + result *= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_div() { + bit<32> result = hdr.p4calc.operand_a; + result /= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_mod() { + bit<32> result = hdr.p4calc.operand_a; + result %= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_band() { + bit<32> result = hdr.p4calc.operand_a; + result &= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_bor() { + bit<32> result = hdr.p4calc.operand_a; + result |= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_bxor() { + bit<32> result = hdr.p4calc.operand_a; + result ^= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_shl() { + bit<32> result = hdr.p4calc.operand_a; + result <<= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_shr() { + bit<32> result = hdr.p4calc.operand_a; + result >>= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_addsat() { + bit<32> result = hdr.p4calc.operand_a; + result |+|= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + action operation_subsat() { + bit<32> result = hdr.p4calc.operand_a; + result |-|= hdr.p4calc.operand_b; + hdr.p4calc.res = result; + } + table calculate { + key = { + hdr.p4calc.op: exact; + } + actions = { + operation_add; + operation_sub; + operation_mul; + operation_div; + operation_mod; + operation_band; + operation_bor; + operation_bxor; + operation_shl; + operation_shr; + operation_addsat; + operation_subsat; + NoAction; + } + const default_action = NoAction(); + const entries = { + P4CALC_ADD : operation_add(); + P4CALC_SUB : operation_sub(); + P4CALC_MUL : operation_mul(); + P4CALC_DIV : operation_div(); + P4CALC_MOD : operation_mod(); + P4CALC_BAND : operation_band(); + P4CALC_BOR : operation_bor(); + P4CALC_BXOR : operation_bxor(); + P4CALC_SHL : operation_shl(); + P4CALC_SHR : operation_shr(); + P4CALC_SATADD : operation_addsat(); + P4CALC_SATSUB : operation_subsat(); + } + } + apply { + if (hdr.p4calc.isValid()) { + calculate.apply(); + } + } +} + +control Ingress(inout H hdr); +package s(Ingress ig); +s(caller()) main; diff --git a/testdata/p4_16_errors_outputs/compound-assignment.p4-stderr b/testdata/p4_16_errors_outputs/compound-assignment.p4-stderr new file mode 100644 index 00000000000..00eebff8282 --- /dev/null +++ b/testdata/p4_16_errors_outputs/compound-assignment.p4-stderr @@ -0,0 +1,6 @@ +compound-assignment.p4(65): [--Werror=invalid] error: hdr.p4calc.operand_a / hdr.p4calc.operand_b: could not evaluate expression at compilation time + hdr.p4calc.res = result; + ^^^^^^ +compound-assignment.p4(71): [--Werror=invalid] error: hdr.p4calc.operand_a % hdr.p4calc.operand_b: could not evaluate expression at compilation time + hdr.p4calc.res = result; + ^^^^^^ diff --git a/testdata/p4_16_samples/opassign1-bmv2.p4 b/testdata/p4_16_samples/opassign1-bmv2.p4 new file mode 100644 index 00000000000..f10fb0f6b0c --- /dev/null +++ b/testdata/p4_16_samples/opassign1-bmv2.p4 @@ -0,0 +1,69 @@ +#include +#include + +header data_t { + bit<32> f1; + bit<32> f2; + bit<16> h1; + bit<16> h2; + bit<8> b1; + bit<8> b2; + bit<8> b3; + bit<8> b4; +} + +struct metadata {} + +struct headers { + data_t data; +} + +parser p(packet_in b, + out headers hdr, + inout metadata meta, + inout standard_metadata_t stdmeta) { + state start { + b.extract(hdr.data); + transition accept; + } +} + +control ingress(inout headers hdr, + inout metadata meta, + inout standard_metadata_t stdmeta) { + apply { + hdr.data.h1 += hdr.data.h2; + hdr.data.b1 -= hdr.data.b2; + hdr.data.b3 ^= hdr.data.b4; + } +} + +control egress(inout headers hdr, + inout metadata meta, + inout standard_metadata_t stdmeta) { + apply {} +} + +control vc(inout headers hdr, + inout metadata meta) { + apply {} +} + +control uc(inout headers hdr, + inout metadata meta) { + apply {} +} + +control deparser(packet_out packet, + in headers hdr) { + apply { + packet.emit(hdr); + } +} + +V1Switch(p(), + vc(), + ingress(), + egress(), + uc(), + deparser()) main; diff --git a/testdata/p4_16_samples/opassign1-bmv2.stf b/testdata/p4_16_samples/opassign1-bmv2.stf new file mode 100644 index 00000000000..97b1faa6fbf --- /dev/null +++ b/testdata/p4_16_samples/opassign1-bmv2.stf @@ -0,0 +1,2 @@ +packet 0 12345678 12345678 0103 0302 40 05 0F 13 +expect 0 12345678 12345678 0405 0302 3B 05 1C 13 diff --git a/testdata/p4_16_samples/opassign1.p4 b/testdata/p4_16_samples/opassign1.p4 new file mode 100644 index 00000000000..1b1965a7eb2 --- /dev/null +++ b/testdata/p4_16_samples/opassign1.p4 @@ -0,0 +1,31 @@ +#include +control generic(inout M m); +package top(generic c); + +@test_keep_opassign + +header t1 { + bit<32> f1; + bit<32> f2; + bit<32> f3; + bit<32> f4; + bit<8> b1; + bit<8> b2; + bit<8> b3; +} + +struct headers_t { + t1 head; +} + +control c(inout headers_t hdrs) { + apply { + hdrs.head.f1 += hdrs.head.f2; + hdrs.head.f1 *= hdrs.head.f3; + hdrs.head.f1 %= hdrs.head.f4; + hdrs.head.f1 <<= hdrs.head.b1; + hdrs.head.f1 >>= hdrs.head.b2; + } +} + +top(c()) main; diff --git a/testdata/p4_16_samples/opassign2-bmv2.p4 b/testdata/p4_16_samples/opassign2-bmv2.p4 new file mode 100644 index 00000000000..1b229c09c75 --- /dev/null +++ b/testdata/p4_16_samples/opassign2-bmv2.p4 @@ -0,0 +1,89 @@ +#include +#include + +header data_t { + bit<32> f1; + bit<32> f2; + bit<16> h1; + bit<16> h2; + bit<8> b1; + bit<8> b2; + bit<8> b3; + bit<8> b4; +} + +header word_t { + bit<32> x; +} + +struct metadata {} + +struct headers { + data_t data; + word_t[8] rest; +} + +parser p(packet_in b, + out headers hdr, + inout metadata meta, + inout standard_metadata_t stdmeta) { + state start { + b.extract(hdr.data); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + transition accept; + } +} + +bit<8> postincr(inout bit<8> x) { + bit<8> rv = x; + x += 1; + return rv; +} + +control ingress(inout headers hdr, + inout metadata meta, + inout standard_metadata_t stdmeta) { + apply { + if (hdr.rest[7].isValid()) { + hdr.rest[postincr(hdr.data.b1) & 7].x |= hdr.data.f1; + hdr.rest[postincr(hdr.data.b1) & 7].x |= hdr.data.f2; + } + } +} + +control egress(inout headers hdr, + inout metadata meta, + inout standard_metadata_t stdmeta) { + apply {} +} + +control vc(inout headers hdr, + inout metadata meta) { + apply {} +} + +control uc(inout headers hdr, + inout metadata meta) { + apply {} +} + +control deparser(packet_out packet, + in headers hdr) { + apply { + packet.emit(hdr); + } +} + +V1Switch(p(), + vc(), + ingress(), + egress(), + uc(), + deparser()) main; diff --git a/testdata/p4_16_samples/opassign2-bmv2.stf b/testdata/p4_16_samples/opassign2-bmv2.stf new file mode 100644 index 00000000000..ce06b24ad53 --- /dev/null +++ b/testdata/p4_16_samples/opassign2-bmv2.stf @@ -0,0 +1,2 @@ +packet 0 00001234 00005678 DEAD DEAD 01 05 FF AA 10000000 20000000 30000000 40000000 50000000 60000000 70000000 80000000 +expect 0 00001234 00005678 DEAD DEAD 03 05 FF AA 10000000 20001234 30005678 40000000 50000000 60000000 70000000 80000000 diff --git a/testdata/p4_16_samples_outputs/opassign1-bmv2-first.p4 b/testdata/p4_16_samples_outputs/opassign1-bmv2-first.p4 new file mode 100644 index 00000000000..e35eb828016 --- /dev/null +++ b/testdata/p4_16_samples_outputs/opassign1-bmv2-first.p4 @@ -0,0 +1,59 @@ +#include +#define V1MODEL_VERSION 20180101 +#include + +header data_t { + bit<32> f1; + bit<32> f2; + bit<16> h1; + bit<16> h2; + bit<8> b1; + bit<8> b2; + bit<8> b3; + bit<8> b4; +} + +struct metadata { +} + +struct headers { + data_t data; +} + +parser p(packet_in b, out headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + state start { + b.extract(hdr.data); + transition accept; + } +} + +control ingress(inout headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + apply { + hdr.data.h1 += hdr.data.h2; + hdr.data.b1 -= hdr.data.b2; + hdr.data.b3 ^= hdr.data.b4; + } +} + +control egress(inout headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + apply { + } +} + +control vc(inout headers hdr, inout metadata meta) { + apply { + } +} + +control uc(inout headers hdr, inout metadata meta) { + apply { + } +} + +control deparser(packet_out packet, in headers hdr) { + apply { + packet.emit(hdr); + } +} + +V1Switch(p(), vc(), ingress(), egress(), uc(), deparser()) main; diff --git a/testdata/p4_16_samples_outputs/opassign1-bmv2-frontend.p4 b/testdata/p4_16_samples_outputs/opassign1-bmv2-frontend.p4 new file mode 100644 index 00000000000..27aa8f24153 --- /dev/null +++ b/testdata/p4_16_samples_outputs/opassign1-bmv2-frontend.p4 @@ -0,0 +1,59 @@ +#include +#define V1MODEL_VERSION 20180101 +#include + +header data_t { + bit<32> f1; + bit<32> f2; + bit<16> h1; + bit<16> h2; + bit<8> b1; + bit<8> b2; + bit<8> b3; + bit<8> b4; +} + +struct metadata { +} + +struct headers { + data_t data; +} + +parser p(packet_in b, out headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + state start { + b.extract(hdr.data); + transition accept; + } +} + +control ingress(inout headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + apply { + hdr.data.h1 = hdr.data.h1 + hdr.data.h2; + hdr.data.b1 = hdr.data.b1 - hdr.data.b2; + hdr.data.b3 = hdr.data.b3 ^ hdr.data.b4; + } +} + +control egress(inout headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + apply { + } +} + +control vc(inout headers hdr, inout metadata meta) { + apply { + } +} + +control uc(inout headers hdr, inout metadata meta) { + apply { + } +} + +control deparser(packet_out packet, in headers hdr) { + apply { + packet.emit(hdr); + } +} + +V1Switch(p(), vc(), ingress(), egress(), uc(), deparser()) main; diff --git a/testdata/p4_16_samples_outputs/opassign1-bmv2-midend.p4 b/testdata/p4_16_samples_outputs/opassign1-bmv2-midend.p4 new file mode 100644 index 00000000000..d9e5f42bec1 --- /dev/null +++ b/testdata/p4_16_samples_outputs/opassign1-bmv2-midend.p4 @@ -0,0 +1,68 @@ +#include +#define V1MODEL_VERSION 20180101 +#include + +header data_t { + bit<32> f1; + bit<32> f2; + bit<16> h1; + bit<16> h2; + bit<8> b1; + bit<8> b2; + bit<8> b3; + bit<8> b4; +} + +struct metadata { +} + +struct headers { + data_t data; +} + +parser p(packet_in b, out headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + state start { + b.extract(hdr.data); + transition accept; + } +} + +control ingress(inout headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + @hidden action opassign1bmv2l35() { + hdr.data.h1 = hdr.data.h1 + hdr.data.h2; + hdr.data.b1 = hdr.data.b1 - hdr.data.b2; + hdr.data.b3 = hdr.data.b3 ^ hdr.data.b4; + } + @hidden table tbl_opassign1bmv2l35 { + actions = { + opassign1bmv2l35(); + } + const default_action = opassign1bmv2l35(); + } + apply { + tbl_opassign1bmv2l35.apply(); + } +} + +control egress(inout headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + apply { + } +} + +control vc(inout headers hdr, inout metadata meta) { + apply { + } +} + +control uc(inout headers hdr, inout metadata meta) { + apply { + } +} + +control deparser(packet_out packet, in headers hdr) { + apply { + packet.emit(hdr.data); + } +} + +V1Switch(p(), vc(), ingress(), egress(), uc(), deparser()) main; diff --git a/testdata/p4_16_samples_outputs/opassign1-bmv2.p4 b/testdata/p4_16_samples_outputs/opassign1-bmv2.p4 new file mode 100644 index 00000000000..2fef66cdcf8 --- /dev/null +++ b/testdata/p4_16_samples_outputs/opassign1-bmv2.p4 @@ -0,0 +1,59 @@ +#include +#define V1MODEL_VERSION 20180101 +#include + +header data_t { + bit<32> f1; + bit<32> f2; + bit<16> h1; + bit<16> h2; + bit<8> b1; + bit<8> b2; + bit<8> b3; + bit<8> b4; +} + +struct metadata { +} + +struct headers { + data_t data; +} + +parser p(packet_in b, out headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + state start { + b.extract(hdr.data); + transition accept; + } +} + +control ingress(inout headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + apply { + hdr.data.h1 += hdr.data.h2; + hdr.data.b1 -= hdr.data.b2; + hdr.data.b3 ^= hdr.data.b4; + } +} + +control egress(inout headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + apply { + } +} + +control vc(inout headers hdr, inout metadata meta) { + apply { + } +} + +control uc(inout headers hdr, inout metadata meta) { + apply { + } +} + +control deparser(packet_out packet, in headers hdr) { + apply { + packet.emit(hdr); + } +} + +V1Switch(p(), vc(), ingress(), egress(), uc(), deparser()) main; diff --git a/testdata/p4_16_samples_outputs/opassign1-bmv2.p4-stderr b/testdata/p4_16_samples_outputs/opassign1-bmv2.p4-stderr new file mode 100644 index 00000000000..e69de29bb2d diff --git a/testdata/p4_16_samples_outputs/opassign1-bmv2.p4.entries.txtpb b/testdata/p4_16_samples_outputs/opassign1-bmv2.p4.entries.txtpb new file mode 100644 index 00000000000..5cb9652623a --- /dev/null +++ b/testdata/p4_16_samples_outputs/opassign1-bmv2.p4.entries.txtpb @@ -0,0 +1,3 @@ +# proto-file: p4/v1/p4runtime.proto +# proto-message: p4.v1.WriteRequest + diff --git a/testdata/p4_16_samples_outputs/opassign1-bmv2.p4.p4info.txtpb b/testdata/p4_16_samples_outputs/opassign1-bmv2.p4.p4info.txtpb new file mode 100644 index 00000000000..fdf16790b91 --- /dev/null +++ b/testdata/p4_16_samples_outputs/opassign1-bmv2.p4.p4info.txtpb @@ -0,0 +1,6 @@ +# proto-file: p4/config/v1/p4info.proto +# proto-message: p4.config.v1.P4Info + +pkg_info { + arch: "v1model" +} diff --git a/testdata/p4_16_samples_outputs/opassign1-first.p4 b/testdata/p4_16_samples_outputs/opassign1-first.p4 new file mode 100644 index 00000000000..11486d58577 --- /dev/null +++ b/testdata/p4_16_samples_outputs/opassign1-first.p4 @@ -0,0 +1,29 @@ +#include + +control generic(inout M m); +package top(generic c); +@test_keep_opassign header t1 { + bit<32> f1; + bit<32> f2; + bit<32> f3; + bit<32> f4; + bit<8> b1; + bit<8> b2; + bit<8> b3; +} + +struct headers_t { + t1 head; +} + +control c(inout headers_t hdrs) { + apply { + hdrs.head.f1 += hdrs.head.f2; + hdrs.head.f1 *= hdrs.head.f3; + hdrs.head.f1 %= hdrs.head.f4; + hdrs.head.f1 <<= hdrs.head.b1; + hdrs.head.f1 >>= hdrs.head.b2; + } +} + +top(c()) main; diff --git a/testdata/p4_16_samples_outputs/opassign1-frontend.p4 b/testdata/p4_16_samples_outputs/opassign1-frontend.p4 new file mode 100644 index 00000000000..11486d58577 --- /dev/null +++ b/testdata/p4_16_samples_outputs/opassign1-frontend.p4 @@ -0,0 +1,29 @@ +#include + +control generic(inout M m); +package top(generic c); +@test_keep_opassign header t1 { + bit<32> f1; + bit<32> f2; + bit<32> f3; + bit<32> f4; + bit<8> b1; + bit<8> b2; + bit<8> b3; +} + +struct headers_t { + t1 head; +} + +control c(inout headers_t hdrs) { + apply { + hdrs.head.f1 += hdrs.head.f2; + hdrs.head.f1 *= hdrs.head.f3; + hdrs.head.f1 %= hdrs.head.f4; + hdrs.head.f1 <<= hdrs.head.b1; + hdrs.head.f1 >>= hdrs.head.b2; + } +} + +top(c()) main; diff --git a/testdata/p4_16_samples_outputs/opassign1-midend.p4 b/testdata/p4_16_samples_outputs/opassign1-midend.p4 new file mode 100644 index 00000000000..77b89049b71 --- /dev/null +++ b/testdata/p4_16_samples_outputs/opassign1-midend.p4 @@ -0,0 +1,38 @@ +#include + +control generic(inout M m); +package top(generic c); +@test_keep_opassign header t1 { + bit<32> f1; + bit<32> f2; + bit<32> f3; + bit<32> f4; + bit<8> b1; + bit<8> b2; + bit<8> b3; +} + +struct headers_t { + t1 head; +} + +control c(inout headers_t hdrs) { + @hidden action opassign1l23() { + hdrs.head.f1 += hdrs.head.f2; + hdrs.head.f1 *= hdrs.head.f3; + hdrs.head.f1 %= hdrs.head.f4; + hdrs.head.f1 <<= hdrs.head.b1; + hdrs.head.f1 >>= hdrs.head.b2; + } + @hidden table tbl_opassign1l23 { + actions = { + opassign1l23(); + } + const default_action = opassign1l23(); + } + apply { + tbl_opassign1l23.apply(); + } +} + +top(c()) main; diff --git a/testdata/p4_16_samples_outputs/opassign1.p4 b/testdata/p4_16_samples_outputs/opassign1.p4 new file mode 100644 index 00000000000..5c4a48a1d4d --- /dev/null +++ b/testdata/p4_16_samples_outputs/opassign1.p4 @@ -0,0 +1,29 @@ +#include + +control generic(inout M m); +package top(generic c); +@test_keep_opassign header t1 { + bit<32> f1; + bit<32> f2; + bit<32> f3; + bit<32> f4; + bit<8> b1; + bit<8> b2; + bit<8> b3; +} + +struct headers_t { + t1 head; +} + +control c(inout headers_t hdrs) { + apply { + hdrs.head.f1 += hdrs.head.f2; + hdrs.head.f1 *= hdrs.head.f3; + hdrs.head.f1 %= hdrs.head.f4; + hdrs.head.f1 <<= hdrs.head.b1; + hdrs.head.f1 >>= hdrs.head.b2; + } +} + +top(c()) main; diff --git a/testdata/p4_16_samples_outputs/opassign1.p4-stderr b/testdata/p4_16_samples_outputs/opassign1.p4-stderr new file mode 100644 index 00000000000..e69de29bb2d diff --git a/testdata/p4_16_samples_outputs/opassign2-bmv2-first.p4 b/testdata/p4_16_samples_outputs/opassign2-bmv2-first.p4 new file mode 100644 index 00000000000..2ead7cde323 --- /dev/null +++ b/testdata/p4_16_samples_outputs/opassign2-bmv2-first.p4 @@ -0,0 +1,78 @@ +#include +#define V1MODEL_VERSION 20180101 +#include + +header data_t { + bit<32> f1; + bit<32> f2; + bit<16> h1; + bit<16> h2; + bit<8> b1; + bit<8> b2; + bit<8> b3; + bit<8> b4; +} + +header word_t { + bit<32> x; +} + +struct metadata { +} + +struct headers { + data_t data; + word_t[8] rest; +} + +parser p(packet_in b, out headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + state start { + b.extract(hdr.data); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + transition accept; + } +} + +bit<8> postincr(inout bit<8> x) { + bit<8> rv = x; + x += 8w1; + return rv; +} +control ingress(inout headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + apply { + if (hdr.rest[7].isValid()) { + hdr.rest[postincr(hdr.data.b1) & 8w7].x |= hdr.data.f1; + hdr.rest[postincr(hdr.data.b1) & 8w7].x |= hdr.data.f2; + } + } +} + +control egress(inout headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + apply { + } +} + +control vc(inout headers hdr, inout metadata meta) { + apply { + } +} + +control uc(inout headers hdr, inout metadata meta) { + apply { + } +} + +control deparser(packet_out packet, in headers hdr) { + apply { + packet.emit(hdr); + } +} + +V1Switch(p(), vc(), ingress(), egress(), uc(), deparser()) main; diff --git a/testdata/p4_16_samples_outputs/opassign2-bmv2-frontend.p4 b/testdata/p4_16_samples_outputs/opassign2-bmv2-frontend.p4 new file mode 100644 index 00000000000..4517a517efe --- /dev/null +++ b/testdata/p4_16_samples_outputs/opassign2-bmv2-frontend.p4 @@ -0,0 +1,97 @@ +#include +#define V1MODEL_VERSION 20180101 +#include + +header data_t { + bit<32> f1; + bit<32> f2; + bit<16> h1; + bit<16> h2; + bit<8> b1; + bit<8> b2; + bit<8> b3; + bit<8> b4; +} + +header word_t { + bit<32> x; +} + +struct metadata { +} + +struct headers { + data_t data; + word_t[8] rest; +} + +parser p(packet_in b, out headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + state start { + b.extract(hdr.data); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + transition accept; + } +} + +control ingress(inout headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + @name("ingress.tmp") bit<8> tmp; + @name("ingress.tmp_0") bit<8> tmp_0; + @name("ingress.tmp_1") bit<8> tmp_1; + @name("ingress.tmp_2") bit<8> tmp_2; + @name("ingress.x_0") bit<8> x_2; + @name("ingress.retval") bit<8> retval; + @name("ingress.rv") bit<8> rv_0; + @name("ingress.x_1") bit<8> x_3; + @name("ingress.retval") bit<8> retval_1; + @name("ingress.rv") bit<8> rv_1; + apply { + if (hdr.rest[7].isValid()) { + x_2 = hdr.data.b1; + rv_0 = x_2; + x_2 = x_2 + 8w1; + retval = rv_0; + hdr.data.b1 = x_2; + tmp = retval; + tmp_0 = tmp & 8w7; + hdr.rest[tmp_0].x = hdr.rest[tmp_0].x | hdr.data.f1; + x_3 = hdr.data.b1; + rv_1 = x_3; + x_3 = x_3 + 8w1; + retval_1 = rv_1; + hdr.data.b1 = x_3; + tmp_1 = retval_1; + tmp_2 = tmp_1 & 8w7; + hdr.rest[tmp_2].x = hdr.rest[tmp_2].x | hdr.data.f2; + } + } +} + +control egress(inout headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + apply { + } +} + +control vc(inout headers hdr, inout metadata meta) { + apply { + } +} + +control uc(inout headers hdr, inout metadata meta) { + apply { + } +} + +control deparser(packet_out packet, in headers hdr) { + apply { + packet.emit(hdr); + } +} + +V1Switch(p(), vc(), ingress(), egress(), uc(), deparser()) main; diff --git a/testdata/p4_16_samples_outputs/opassign2-bmv2-midend.p4 b/testdata/p4_16_samples_outputs/opassign2-bmv2-midend.p4 new file mode 100644 index 00000000000..00475437abd --- /dev/null +++ b/testdata/p4_16_samples_outputs/opassign2-bmv2-midend.p4 @@ -0,0 +1,285 @@ +#include +#define V1MODEL_VERSION 20180101 +#include + +header data_t { + bit<32> f1; + bit<32> f2; + bit<16> h1; + bit<16> h2; + bit<8> b1; + bit<8> b2; + bit<8> b3; + bit<8> b4; +} + +header word_t { + bit<32> x; +} + +struct metadata { +} + +struct headers { + data_t data; + word_t[8] rest; +} + +parser p(packet_in b, out headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + state start { + b.extract(hdr.data); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + transition accept; + } +} + +control ingress(inout headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + bit<8> hsiVar; + bit<8> hsiVar_0; + @name("ingress.retval") bit<8> retval; + @name("ingress.retval") bit<8> retval_1; + @hidden action opassign2bmv2l55() { + hdr.rest[8w0].x = hdr.rest[8w0].x | hdr.data.f1; + } + @hidden action opassign2bmv2l55_0() { + hdr.rest[8w1].x = hdr.rest[8w1].x | hdr.data.f1; + } + @hidden action opassign2bmv2l55_1() { + hdr.rest[8w2].x = hdr.rest[8w2].x | hdr.data.f1; + } + @hidden action opassign2bmv2l55_2() { + hdr.rest[8w3].x = hdr.rest[8w3].x | hdr.data.f1; + } + @hidden action opassign2bmv2l55_3() { + hdr.rest[8w4].x = hdr.rest[8w4].x | hdr.data.f1; + } + @hidden action opassign2bmv2l55_4() { + hdr.rest[8w5].x = hdr.rest[8w5].x | hdr.data.f1; + } + @hidden action opassign2bmv2l55_5() { + hdr.rest[8w6].x = hdr.rest[8w6].x | hdr.data.f1; + } + @hidden action opassign2bmv2l55_6() { + hdr.rest[8w7].x = hdr.rest[8w7].x | hdr.data.f1; + } + @hidden action opassign2bmv2l47() { + retval = hdr.data.b1; + hdr.data.b1 = hdr.data.b1 + 8w1; + hsiVar = retval & 8w7; + } + @hidden action opassign2bmv2l56() { + hdr.rest[8w0].x = hdr.rest[8w0].x | hdr.data.f2; + } + @hidden action opassign2bmv2l56_0() { + hdr.rest[8w1].x = hdr.rest[8w1].x | hdr.data.f2; + } + @hidden action opassign2bmv2l56_1() { + hdr.rest[8w2].x = hdr.rest[8w2].x | hdr.data.f2; + } + @hidden action opassign2bmv2l56_2() { + hdr.rest[8w3].x = hdr.rest[8w3].x | hdr.data.f2; + } + @hidden action opassign2bmv2l56_3() { + hdr.rest[8w4].x = hdr.rest[8w4].x | hdr.data.f2; + } + @hidden action opassign2bmv2l56_4() { + hdr.rest[8w5].x = hdr.rest[8w5].x | hdr.data.f2; + } + @hidden action opassign2bmv2l56_5() { + hdr.rest[8w6].x = hdr.rest[8w6].x | hdr.data.f2; + } + @hidden action opassign2bmv2l56_6() { + hdr.rest[8w7].x = hdr.rest[8w7].x | hdr.data.f2; + } + @hidden action opassign2bmv2l47_0() { + retval_1 = hdr.data.b1; + hdr.data.b1 = hdr.data.b1 + 8w1; + hsiVar_0 = retval_1 & 8w7; + } + @hidden table tbl_opassign2bmv2l47 { + actions = { + opassign2bmv2l47(); + } + const default_action = opassign2bmv2l47(); + } + @hidden table tbl_opassign2bmv2l55 { + actions = { + opassign2bmv2l55(); + } + const default_action = opassign2bmv2l55(); + } + @hidden table tbl_opassign2bmv2l55_0 { + actions = { + opassign2bmv2l55_0(); + } + const default_action = opassign2bmv2l55_0(); + } + @hidden table tbl_opassign2bmv2l55_1 { + actions = { + opassign2bmv2l55_1(); + } + const default_action = opassign2bmv2l55_1(); + } + @hidden table tbl_opassign2bmv2l55_2 { + actions = { + opassign2bmv2l55_2(); + } + const default_action = opassign2bmv2l55_2(); + } + @hidden table tbl_opassign2bmv2l55_3 { + actions = { + opassign2bmv2l55_3(); + } + const default_action = opassign2bmv2l55_3(); + } + @hidden table tbl_opassign2bmv2l55_4 { + actions = { + opassign2bmv2l55_4(); + } + const default_action = opassign2bmv2l55_4(); + } + @hidden table tbl_opassign2bmv2l55_5 { + actions = { + opassign2bmv2l55_5(); + } + const default_action = opassign2bmv2l55_5(); + } + @hidden table tbl_opassign2bmv2l55_6 { + actions = { + opassign2bmv2l55_6(); + } + const default_action = opassign2bmv2l55_6(); + } + @hidden table tbl_opassign2bmv2l47_0 { + actions = { + opassign2bmv2l47_0(); + } + const default_action = opassign2bmv2l47_0(); + } + @hidden table tbl_opassign2bmv2l56 { + actions = { + opassign2bmv2l56(); + } + const default_action = opassign2bmv2l56(); + } + @hidden table tbl_opassign2bmv2l56_0 { + actions = { + opassign2bmv2l56_0(); + } + const default_action = opassign2bmv2l56_0(); + } + @hidden table tbl_opassign2bmv2l56_1 { + actions = { + opassign2bmv2l56_1(); + } + const default_action = opassign2bmv2l56_1(); + } + @hidden table tbl_opassign2bmv2l56_2 { + actions = { + opassign2bmv2l56_2(); + } + const default_action = opassign2bmv2l56_2(); + } + @hidden table tbl_opassign2bmv2l56_3 { + actions = { + opassign2bmv2l56_3(); + } + const default_action = opassign2bmv2l56_3(); + } + @hidden table tbl_opassign2bmv2l56_4 { + actions = { + opassign2bmv2l56_4(); + } + const default_action = opassign2bmv2l56_4(); + } + @hidden table tbl_opassign2bmv2l56_5 { + actions = { + opassign2bmv2l56_5(); + } + const default_action = opassign2bmv2l56_5(); + } + @hidden table tbl_opassign2bmv2l56_6 { + actions = { + opassign2bmv2l56_6(); + } + const default_action = opassign2bmv2l56_6(); + } + apply { + if (hdr.rest[7].isValid()) { + tbl_opassign2bmv2l47.apply(); + if (hsiVar == 8w0) { + tbl_opassign2bmv2l55.apply(); + } else if (hsiVar == 8w1) { + tbl_opassign2bmv2l55_0.apply(); + } else if (hsiVar == 8w2) { + tbl_opassign2bmv2l55_1.apply(); + } else if (hsiVar == 8w3) { + tbl_opassign2bmv2l55_2.apply(); + } else if (hsiVar == 8w4) { + tbl_opassign2bmv2l55_3.apply(); + } else if (hsiVar == 8w5) { + tbl_opassign2bmv2l55_4.apply(); + } else if (hsiVar == 8w6) { + tbl_opassign2bmv2l55_5.apply(); + } else if (hsiVar == 8w7) { + tbl_opassign2bmv2l55_6.apply(); + } + tbl_opassign2bmv2l47_0.apply(); + if (hsiVar_0 == 8w0) { + tbl_opassign2bmv2l56.apply(); + } else if (hsiVar_0 == 8w1) { + tbl_opassign2bmv2l56_0.apply(); + } else if (hsiVar_0 == 8w2) { + tbl_opassign2bmv2l56_1.apply(); + } else if (hsiVar_0 == 8w3) { + tbl_opassign2bmv2l56_2.apply(); + } else if (hsiVar_0 == 8w4) { + tbl_opassign2bmv2l56_3.apply(); + } else if (hsiVar_0 == 8w5) { + tbl_opassign2bmv2l56_4.apply(); + } else if (hsiVar_0 == 8w6) { + tbl_opassign2bmv2l56_5.apply(); + } else if (hsiVar_0 == 8w7) { + tbl_opassign2bmv2l56_6.apply(); + } + } + } +} + +control egress(inout headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + apply { + } +} + +control vc(inout headers hdr, inout metadata meta) { + apply { + } +} + +control uc(inout headers hdr, inout metadata meta) { + apply { + } +} + +control deparser(packet_out packet, in headers hdr) { + apply { + packet.emit(hdr.data); + packet.emit(hdr.rest[0]); + packet.emit(hdr.rest[1]); + packet.emit(hdr.rest[2]); + packet.emit(hdr.rest[3]); + packet.emit(hdr.rest[4]); + packet.emit(hdr.rest[5]); + packet.emit(hdr.rest[6]); + packet.emit(hdr.rest[7]); + } +} + +V1Switch(p(), vc(), ingress(), egress(), uc(), deparser()) main; diff --git a/testdata/p4_16_samples_outputs/opassign2-bmv2.p4 b/testdata/p4_16_samples_outputs/opassign2-bmv2.p4 new file mode 100644 index 00000000000..1d135a69f24 --- /dev/null +++ b/testdata/p4_16_samples_outputs/opassign2-bmv2.p4 @@ -0,0 +1,78 @@ +#include +#define V1MODEL_VERSION 20180101 +#include + +header data_t { + bit<32> f1; + bit<32> f2; + bit<16> h1; + bit<16> h2; + bit<8> b1; + bit<8> b2; + bit<8> b3; + bit<8> b4; +} + +header word_t { + bit<32> x; +} + +struct metadata { +} + +struct headers { + data_t data; + word_t[8] rest; +} + +parser p(packet_in b, out headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + state start { + b.extract(hdr.data); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + b.extract(hdr.rest.next); + transition accept; + } +} + +bit<8> postincr(inout bit<8> x) { + bit<8> rv = x; + x += 1; + return rv; +} +control ingress(inout headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + apply { + if (hdr.rest[7].isValid()) { + hdr.rest[postincr(hdr.data.b1) & 7].x |= hdr.data.f1; + hdr.rest[postincr(hdr.data.b1) & 7].x |= hdr.data.f2; + } + } +} + +control egress(inout headers hdr, inout metadata meta, inout standard_metadata_t stdmeta) { + apply { + } +} + +control vc(inout headers hdr, inout metadata meta) { + apply { + } +} + +control uc(inout headers hdr, inout metadata meta) { + apply { + } +} + +control deparser(packet_out packet, in headers hdr) { + apply { + packet.emit(hdr); + } +} + +V1Switch(p(), vc(), ingress(), egress(), uc(), deparser()) main; diff --git a/testdata/p4_16_samples_outputs/opassign2-bmv2.p4-stderr b/testdata/p4_16_samples_outputs/opassign2-bmv2.p4-stderr new file mode 100644 index 00000000000..e69de29bb2d diff --git a/testdata/p4_16_samples_outputs/opassign2-bmv2.p4.entries.txtpb b/testdata/p4_16_samples_outputs/opassign2-bmv2.p4.entries.txtpb new file mode 100644 index 00000000000..5cb9652623a --- /dev/null +++ b/testdata/p4_16_samples_outputs/opassign2-bmv2.p4.entries.txtpb @@ -0,0 +1,3 @@ +# proto-file: p4/v1/p4runtime.proto +# proto-message: p4.v1.WriteRequest + diff --git a/testdata/p4_16_samples_outputs/opassign2-bmv2.p4.p4info.txtpb b/testdata/p4_16_samples_outputs/opassign2-bmv2.p4.p4info.txtpb new file mode 100644 index 00000000000..fdf16790b91 --- /dev/null +++ b/testdata/p4_16_samples_outputs/opassign2-bmv2.p4.p4info.txtpb @@ -0,0 +1,6 @@ +# proto-file: p4/config/v1/p4info.proto +# proto-message: p4.config.v1.P4Info + +pkg_info { + arch: "v1model" +} diff --git a/testdata/p4tc_samples_stf/arp_respond.p4 b/testdata/p4tc_samples_stf/arp_respond.p4 new file mode 100644 index 00000000000..35e6525b500 --- /dev/null +++ b/testdata/p4tc_samples_stf/arp_respond.p4 @@ -0,0 +1,143 @@ +/* -*- P4_16 -*- */ + +#include +#include + +struct my_ingress_metadata_t { +} + +struct empty_metadata_t { +} + +/* + * Standard ethernet header + */ +header ethernet_t { + @tc_type("macaddr") bit<48> dstAddr; + @tc_type("macaddr") bit<48> srcAddr; + bit<16> etherType; +} + +header arp_t { + bit<16> htype; + bit<16> ptype; + bit<8> hlen; + bit<8> plen; + bit<16> oper; +} + +header arp_ipv4_t { + @tc_type("macaddr") bit<48> sha; + @tc_type("ipv4") bit<32> spa; + @tc_type("macaddr") bit<48> tha; + @tc_type("ipv4") bit<32> tpa; +} + +struct my_ingress_headers_t { + ethernet_t ethernet; + arp_t arp; + arp_ipv4_t arp_ipv4; +} + +const bit<16> ETHERTYPE_ARP = 0x0806; +const bit<16> ARP_HTYPE = 0x0001; +const bit<16> ARP_PTYPE = 0x0800; +const bit<8> ARP_HLEN = 6; +const bit<8> ARP_PLEN = 4; +const bit<16> ARP_REQ = 1; +const bit<16> ARP_REPLY = 2; + +/*********************** P A R S E R **************************/ +parser Ingress_Parser( + packet_in pkt, + out my_ingress_headers_t hdr, + inout my_ingress_metadata_t meta, + in pna_main_parser_input_metadata_t istd) +{ + state start { + transition parse_ethernet; + } + + state parse_ethernet { + pkt.extract(hdr.ethernet); + transition select(hdr.ethernet.etherType) { + ETHERTYPE_ARP: parse_arp; + default: accept; + } + } + + state parse_arp { + pkt.extract(hdr.arp); + pkt.extract(hdr.arp_ipv4); + transition select(hdr.arp.oper) { + ARP_REQ: accept; + default: reject; + } + } +} + +#define ARP_TABLE_SIZE 1024 + +/***************** M A T C H - A C T I O N *********************/ + +control ingress( + inout my_ingress_headers_t hdr, + inout my_ingress_metadata_t meta, + in pna_main_input_metadata_t istd, + inout pna_main_output_metadata_t ostd +) +{ + /* Generate a grat ARP */ + action arp_reply(@tc_type("macaddr") bit<48> rmac) { + hdr.arp.oper = ARP_REPLY; + hdr.arp_ipv4.tha = hdr.arp_ipv4.sha; + hdr.arp_ipv4.sha = rmac; + hdr.arp_ipv4.spa = hdr.arp_ipv4.tpa; + hdr.ethernet.dstAddr = hdr.ethernet.srcAddr; + hdr.ethernet.srcAddr = rmac; + send_to_port(istd.input_port); + } + + action drop() { + drop_packet(); + } + + table arp_table { + key = { + hdr.arp_ipv4.tpa : exact @tc_type("ipv4") @name("IPaddr"); + } + actions = { + arp_reply; + drop; + } + size = ARP_TABLE_SIZE; + const default_action = drop; + } + + apply { + arp_table.apply(); + } +} + + /********************* D E P A R S E R ************************/ + +control Ingress_Deparser( + packet_out pkt, + inout my_ingress_headers_t hdr, + in my_ingress_metadata_t meta, + in pna_main_output_metadata_t ostd) +{ + apply { + pkt.emit(hdr.ethernet); + pkt.emit(hdr.arp); + pkt.emit(hdr.arp_ipv4); + } +} + +/************ F I N A L P A C K A G E ******************************/ + +PNA_NIC( + Ingress_Parser(), + ingress(), + Ingress_Deparser() +) main; diff --git a/testdata/p4tc_samples_stf/arp_respond.stf b/testdata/p4tc_samples_stf/arp_respond.stf new file mode 100644 index 00000000000..da926d32139 --- /dev/null +++ b/testdata/p4tc_samples_stf/arp_respond.stf @@ -0,0 +1,4 @@ +add ingress.arp_table IPaddr:0x0a000014 ingress.arp_reply(rmac:0x000102030420) + +packet 0 ffffffff ffff1000 0001aabb 08060001 08000604 00011000 0001aabb 0a00000 100000000 00000a00 0014 +expect 0 10000001 aabb0001 02030420 08060001 08000604 00020001 02030420 0a000014 10000001 aabb0a00 0014 diff --git a/testdata/p4tc_samples_stf/simple_l3.p4 b/testdata/p4tc_samples_stf/simple_l3.p4 new file mode 100644 index 00000000000..04e9db71434 --- /dev/null +++ b/testdata/p4tc_samples_stf/simple_l3.p4 @@ -0,0 +1,135 @@ +/* -*- P4_16 -*- */ + +#include +#include + +struct my_ingress_metadata_t { +} + +struct empty_metadata_t { +} + +/* + * CONST VALUES FOR TYPES + */ +const bit<8> IP_PROTO_TCP = 0x06; +const bit<16> ETHERTYPE_IPV4 = 0x0800; + +/* + * Standard ethernet header + */ +header ethernet_t { + @tc_type("macaddr") bit<48> dstAddr; + @tc_type("macaddr") bit<48> srcAddr; + bit<16> etherType; +} + +header ipv4_t { + bit<4> version; + bit<4> ihl; + bit<8> diffserv; + bit<16> totalLen; + bit<16> identification; + bit<3> flags; + bit<13> fragOffset; + bit<8> ttl; + bit<8> protocol; + bit<16> hdrChecksum; + @tc_type("ipv4") bit<32> srcAddr; + @tc_type("ipv4") bit<32> dstAddr; +} + +struct my_ingress_headers_t { + ethernet_t ethernet; + ipv4_t ipv4; +} + + /*********************** P A R S E R **************************/ +parser Ingress_Parser( + packet_in pkt, + out my_ingress_headers_t hdr, + inout my_ingress_metadata_t meta, + in pna_main_parser_input_metadata_t istd) +{ + + state start { + transition parse_ethernet; + } + + state parse_ethernet { + pkt.extract(hdr.ethernet); + transition select(hdr.ethernet.etherType) { + ETHERTYPE_IPV4: parse_ipv4; + default: reject; + } + } + state parse_ipv4 { + pkt.extract(hdr.ipv4); + transition select(hdr.ipv4.protocol) { + IP_PROTO_TCP : accept; + default: reject; + } + } +} + +#define L3_TABLE_SIZE 2048 + +/***************** M A T C H - A C T I O N *********************/ + +control ingress( + inout my_ingress_headers_t hdr, + inout my_ingress_metadata_t meta, + in pna_main_input_metadata_t istd, + inout pna_main_output_metadata_t ostd +) +{ + action send_nh(@tc_type("dev") PortId_t port, @tc_type("macaddr") bit<48> srcMac, @tc_type("macaddr") bit<48> dstMac) { + hdr.ethernet.srcAddr = srcMac; + hdr.ethernet.dstAddr = dstMac; + send_to_port(port); + } + + action drop() { + drop_packet(); + } + + table nh_table { + key = { + hdr.ipv4.dstAddr : exact @tc_type("ipv4") @name("dstAddr"); + } + actions = { + send_nh; + drop; + } + size = L3_TABLE_SIZE; + const default_action = drop; + } + + apply { + if (hdr.ipv4.isValid() && hdr.ipv4.protocol == IP_PROTO_TCP) { + nh_table.apply(); + } + } +} + + /********************* D E P A R S E R ************************/ + +control Ingress_Deparser( + packet_out pkt, + inout my_ingress_headers_t hdr, + in my_ingress_metadata_t meta, + in pna_main_output_metadata_t ostd) +{ + apply { + pkt.emit(hdr.ethernet); + pkt.emit(hdr.ipv4); + } +} + +/************ F I N A L P A C K A G E ******************************/ + +PNA_NIC( + Ingress_Parser(), + ingress(), + Ingress_Deparser() +) main; diff --git a/testdata/p4tc_samples_stf/simple_l3.stf b/testdata/p4tc_samples_stf/simple_l3.stf new file mode 100644 index 00000000000..caf9669fea6 --- /dev/null +++ b/testdata/p4tc_samples_stf/simple_l3.stf @@ -0,0 +1,5 @@ +add ingress.nh_table dstAddr:0x0a000002 ingress.send_nh(port:0x1, srcMac:0x000102030405, dstMac:0xAABBCCDDEEFF) + +packet 0 10000002 aabb1000 0001aabb 08004500 0028613d 00004006 05910a00 00010a0 0000209f e0000364 95b780fb 14bc6500 00200a2a b0000 + +expect 1 aabbccdd eeff0001 02030405 08004500 0028613d 00004006 05910a00 00010a0 0000209f e0000364 95b780fb 14bc6500 00200a2a b0000 diff --git a/testdata/p4tc_samples_stf_outputs/arp_respond.json b/testdata/p4tc_samples_stf_outputs/arp_respond.json new file mode 100644 index 00000000000..cd608832c76 --- /dev/null +++ b/testdata/p4tc_samples_stf_outputs/arp_respond.json @@ -0,0 +1,51 @@ +{ + "schema_version" : "1.0.0", + "pipeline_name" : "arp_respond", + "externs" : [], + "tables" : [ + { + "name" : "ingress/arp_table", + "id" : 1, + "tentries" : 1024, + "permissions" : "0x3da4", + "nummask" : 8, + "keysize" : 32, + "keyfields" : [ + { + "id" : 1, + "name" : "IPaddr", + "type" : "ipv4", + "match_type" : "exact", + "bitwidth" : 32 + } + ], + "actions" : [ + { + "id" : 1, + "name" : "ingress/arp_reply", + "action_scope" : "TableAndDefault", + "annotations" : [], + "params" : [ + { + "id" : 1, + "name" : "rmac", + "type" : "macaddr", + "bitwidth" : 48 + } + ], + "default_hit_action" : false, + "default_miss_action" : false + }, + { + "id" : 2, + "name" : "ingress/drop", + "action_scope" : "TableAndDefault", + "annotations" : [], + "params" : [], + "default_hit_action" : false, + "default_miss_action" : true + } + ] + } + ] +} \ No newline at end of file diff --git a/testdata/p4tc_samples_stf_outputs/arp_respond.template b/testdata/p4tc_samples_stf_outputs/arp_respond.template new file mode 100755 index 00000000000..5b344146506 --- /dev/null +++ b/testdata/p4tc_samples_stf_outputs/arp_respond.template @@ -0,0 +1,22 @@ +#!/bin/bash -x + +set -e + +: "${TC:="tc"}" +$TC p4template create pipeline/arp_respond numtables 1 + +$TC p4template create action/arp_respond/ingress/arp_reply actid 1 \ + param rmac type macaddr +$TC p4template update action/arp_respond/ingress/arp_reply state active + +$TC p4template create action/arp_respond/ingress/drop actid 2 +$TC p4template update action/arp_respond/ingress/drop state active + +$TC p4template create table/arp_respond/ingress/arp_table \ + tblid 1 \ + type exact \ + keysz 32 permissions 0x3da4 tentries 1024 nummasks 1 \ + table_acts act name arp_respond/ingress/arp_reply \ + act name arp_respond/ingress/drop +$TC p4template update table/arp_respond/ingress/arp_table default_miss_action permissions 0x1024 action arp_respond/ingress/drop +$TC p4template update pipeline/arp_respond state ready \ No newline at end of file diff --git a/testdata/p4tc_samples_stf_outputs/arp_respond_control_blocks.c b/testdata/p4tc_samples_stf_outputs/arp_respond_control_blocks.c new file mode 100644 index 00000000000..6adf44c208c --- /dev/null +++ b/testdata/p4tc_samples_stf_outputs/arp_respond_control_blocks.c @@ -0,0 +1,335 @@ +#include "arp_respond_parser.h" +struct p4tc_filter_fields p4tc_filter_fields; + +struct internal_metadata { + __u16 pkt_ether_type; +} __attribute__((aligned(4))); + +struct skb_aggregate { + struct p4tc_skb_meta_get get; + struct p4tc_skb_meta_set set; +}; + +struct __attribute__((__packed__)) ingress_arp_table_key { + u32 keysz; + u32 maskid; + u32 field0; /* hdr.arp_ipv4.tpa */ +} __attribute__((aligned(8))); +#define INGRESS_ARP_TABLE_ACT_INGRESS_ARP_REPLY 1 +#define INGRESS_ARP_TABLE_ACT_INGRESS_DROP 2 +#define INGRESS_ARP_TABLE_ACT_NOACTION 0 +struct __attribute__((__packed__)) ingress_arp_table_value { + unsigned int action; + u32 hit:1, + is_default_miss_act:1, + is_default_hit_act:1; + union { + struct { + } _NoAction; + struct __attribute__((__packed__)) { + u8 rmac[6]; + } ingress_arp_reply; + struct { + } ingress_drop; + } u; +}; + +static __always_inline int process(struct __sk_buff *skb, struct my_ingress_headers_t *hdr, struct pna_global_metadata *compiler_meta__, struct skb_aggregate *sa) +{ + struct hdr_md *hdrMd; + + unsigned ebpf_packetOffsetInBits_save = 0; + ParserError_t ebpf_errorCode = NoError; + void* pkt = ((void*)(long)skb->data); + u8* hdr_start = pkt; + void* ebpf_packetEnd = ((void*)(long)skb->data_end); + u32 ebpf_zero = 0; + u32 ebpf_one = 1; + unsigned char ebpf_byte; + u32 pkt_len = skb->len; + + struct my_ingress_metadata_t *meta; + hdrMd = BPF_MAP_LOOKUP_ELEM(hdr_md_cpumap, &ebpf_zero); + if (!hdrMd) + return TC_ACT_SHOT; + unsigned ebpf_packetOffsetInBits = hdrMd->ebpf_packetOffsetInBits; + hdr_start = pkt + BYTES(ebpf_packetOffsetInBits); + hdr = &(hdrMd->cpumap_hdr); + meta = &(hdrMd->cpumap_usermeta); +{ + u8 hit; + { +/* arp_table_0.apply() */ + { + /* construct key */ + struct p4tc_table_entry_act_bpf_params__local params = { + .pipeid = p4tc_filter_fields.pipeid, + .tblid = 1 + }; + struct ingress_arp_table_key key; + __builtin_memset(&key, 0, sizeof(key)); + key.keysz = 32; + key.field0 = hdr->arp_ipv4.tpa; + struct p4tc_table_entry_act_bpf *act_bpf; + /* value */ + struct ingress_arp_table_value *value = NULL; + /* perform lookup */ + act_bpf = bpf_p4tc_tbl_read(skb, ¶ms, sizeof(params), &key, sizeof(key)); + value = (struct ingress_arp_table_value *)act_bpf; + if (value == NULL) { + /* miss; find default action */ + hit = 0; + } else { + hit = value->hit; + } + if (value != NULL) { + /* run action */ + switch (value->action) { + case INGRESS_ARP_TABLE_ACT_INGRESS_ARP_REPLY: + { + hdr->arp.oper = 2; + storePrimitive64((u8 *)&hdr->arp_ipv4.tha, 48, (getPrimitive64((u8 *)hdr->arp_ipv4.sha, 48))); + storePrimitive64((u8 *)&hdr->arp_ipv4.sha, 48, (getPrimitive64((u8 *)value->u.ingress_arp_reply.rmac, 48))); + hdr->arp_ipv4.spa = hdr->arp_ipv4.tpa; + storePrimitive64((u8 *)&hdr->ethernet.dstAddr, 48, (getPrimitive64((u8 *)hdr->ethernet.srcAddr, 48))); + storePrimitive64((u8 *)&hdr->ethernet.srcAddr, 48, (getPrimitive64((u8 *)value->u.ingress_arp_reply.rmac, 48))); + /* send_to_port(skb->ifindex) */ + compiler_meta__->drop = false; + send_to_port(skb->ifindex); + } + break; + case INGRESS_ARP_TABLE_ACT_INGRESS_DROP: + { +/* drop_packet() */ + drop_packet(); + } + break; + case INGRESS_ARP_TABLE_ACT_NOACTION: + { + } + break; + } + } else { + } + } +; + } + } + { +{ +; + ; + ; + } + + if (compiler_meta__->drop) { + return TC_ACT_SHOT; + } + int outHeaderLength = 0; + if (hdr->ethernet.ebpf_valid) { + outHeaderLength += 112; + } +; if (hdr->arp.ebpf_valid) { + outHeaderLength += 64; + } +; if (hdr->arp_ipv4.ebpf_valid) { + outHeaderLength += 160; + } +; + __u16 saved_proto = 0; + bool have_saved_proto = false; + // bpf_skb_adjust_room works only when protocol is IPv4 or IPv6 + // 0x0800 = IPv4, 0x86dd = IPv6 + if ((skb->protocol != bpf_htons(0x0800)) && (skb->protocol != bpf_htons(0x86dd))) { + saved_proto = skb->protocol; + have_saved_proto = true; + bpf_p4tc_skb_set_protocol(skb, &sa->set, bpf_htons(0x0800)); + bpf_p4tc_skb_meta_set(skb, &sa->set, sizeof(sa->set)); + } + ; + + int outHeaderOffset = BYTES(outHeaderLength) - (hdr_start - (u8*)pkt); + if (outHeaderOffset != 0) { + int returnCode = 0; + returnCode = bpf_skb_adjust_room(skb, outHeaderOffset, 1, 0); + if (returnCode) { + return TC_ACT_SHOT; + } + } + + if (have_saved_proto) { + bpf_p4tc_skb_set_protocol(skb, &sa->set, saved_proto); + bpf_p4tc_skb_meta_set(skb, &sa->set, sizeof(sa->set)); + } + + pkt = ((void*)(long)skb->data); + ebpf_packetEnd = ((void*)(long)skb->data_end); + ebpf_packetOffsetInBits = 0; + if (hdr->ethernet.ebpf_valid) { + if (ebpf_packetEnd < pkt + BYTES(ebpf_packetOffsetInBits + 112)) { + return TC_ACT_SHOT; + } + + ebpf_byte = ((char*)(&hdr->ethernet.dstAddr))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.dstAddr))[1]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 1, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.dstAddr))[2]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 2, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.dstAddr))[3]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 3, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.dstAddr))[4]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 4, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.dstAddr))[5]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 5, (ebpf_byte)); + ebpf_packetOffsetInBits += 48; + + ebpf_byte = ((char*)(&hdr->ethernet.srcAddr))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.srcAddr))[1]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 1, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.srcAddr))[2]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 2, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.srcAddr))[3]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 3, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.srcAddr))[4]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 4, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.srcAddr))[5]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 5, (ebpf_byte)); + ebpf_packetOffsetInBits += 48; + + hdr->ethernet.etherType = bpf_htons(hdr->ethernet.etherType); + ebpf_byte = ((char*)(&hdr->ethernet.etherType))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.etherType))[1]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 1, (ebpf_byte)); + ebpf_packetOffsetInBits += 16; + + } +; if (hdr->arp.ebpf_valid) { + if (ebpf_packetEnd < pkt + BYTES(ebpf_packetOffsetInBits + 64)) { + return TC_ACT_SHOT; + } + + hdr->arp.htype = bpf_htons(hdr->arp.htype); + ebpf_byte = ((char*)(&hdr->arp.htype))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->arp.htype))[1]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 1, (ebpf_byte)); + ebpf_packetOffsetInBits += 16; + + hdr->arp.ptype = bpf_htons(hdr->arp.ptype); + ebpf_byte = ((char*)(&hdr->arp.ptype))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->arp.ptype))[1]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 1, (ebpf_byte)); + ebpf_packetOffsetInBits += 16; + + ebpf_byte = ((char*)(&hdr->arp.hlen))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_packetOffsetInBits += 8; + + ebpf_byte = ((char*)(&hdr->arp.plen))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_packetOffsetInBits += 8; + + hdr->arp.oper = bpf_htons(hdr->arp.oper); + ebpf_byte = ((char*)(&hdr->arp.oper))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->arp.oper))[1]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 1, (ebpf_byte)); + ebpf_packetOffsetInBits += 16; + + } +; if (hdr->arp_ipv4.ebpf_valid) { + if (ebpf_packetEnd < pkt + BYTES(ebpf_packetOffsetInBits + 160)) { + return TC_ACT_SHOT; + } + + ebpf_byte = ((char*)(&hdr->arp_ipv4.sha))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->arp_ipv4.sha))[1]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 1, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->arp_ipv4.sha))[2]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 2, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->arp_ipv4.sha))[3]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 3, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->arp_ipv4.sha))[4]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 4, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->arp_ipv4.sha))[5]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 5, (ebpf_byte)); + ebpf_packetOffsetInBits += 48; + + ebpf_byte = ((char*)(&hdr->arp_ipv4.spa))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->arp_ipv4.spa))[1]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 1, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->arp_ipv4.spa))[2]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 2, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->arp_ipv4.spa))[3]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 3, (ebpf_byte)); + ebpf_packetOffsetInBits += 32; + + ebpf_byte = ((char*)(&hdr->arp_ipv4.tha))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->arp_ipv4.tha))[1]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 1, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->arp_ipv4.tha))[2]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 2, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->arp_ipv4.tha))[3]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 3, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->arp_ipv4.tha))[4]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 4, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->arp_ipv4.tha))[5]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 5, (ebpf_byte)); + ebpf_packetOffsetInBits += 48; + + ebpf_byte = ((char*)(&hdr->arp_ipv4.tpa))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->arp_ipv4.tpa))[1]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 1, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->arp_ipv4.tpa))[2]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 2, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->arp_ipv4.tpa))[3]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 3, (ebpf_byte)); + ebpf_packetOffsetInBits += 32; + + } +; + } + return -1; +} +SEC("p4tc/main") +int tc_ingress_func(struct __sk_buff *skb) { + struct skb_aggregate skbstuff; + struct pna_global_metadata *compiler_meta__ = (struct pna_global_metadata *) skb->cb; + compiler_meta__->drop = false; + compiler_meta__->recirculate = false; + compiler_meta__->egress_port = 0; + if (!compiler_meta__->recirculated) { + compiler_meta__->mark = 153; + struct internal_metadata *md = (struct internal_metadata *)(unsigned long)skb->data_meta; + if ((void *) ((struct internal_metadata *) md + 1) <= (void *)(long)skb->data) { + __u16 *ether_type = (__u16 *) ((void *) (long)skb->data + 12); + if ((void *) ((__u16 *) ether_type + 1) > (void *) (long) skb->data_end) { + return TC_ACT_SHOT; + } + *ether_type = md->pkt_ether_type; + } + } + struct hdr_md *hdrMd; + struct my_ingress_headers_t *hdr; + int ret = -1; + ret = process(skb, (struct my_ingress_headers_t *) hdr, compiler_meta__, &skbstuff); + if (ret != -1) { + return ret; + } + if (!compiler_meta__->drop && compiler_meta__->recirculate) { + compiler_meta__->recirculated = true; + return TC_ACT_UNSPEC; + } + if (!compiler_meta__->drop && compiler_meta__->egress_port == 0) + return TC_ACT_OK; + return bpf_redirect(compiler_meta__->egress_port, 0); +} +char _license[] SEC("license") = "GPL"; diff --git a/testdata/p4tc_samples_stf_outputs/arp_respond_parser.c b/testdata/p4tc_samples_stf_outputs/arp_respond_parser.c new file mode 100644 index 00000000000..6f18341a1a1 --- /dev/null +++ b/testdata/p4tc_samples_stf_outputs/arp_respond_parser.c @@ -0,0 +1,142 @@ +#include "arp_respond_parser.h" + +struct p4tc_filter_fields p4tc_filter_fields; + +static __always_inline int run_parser(struct __sk_buff *skb, struct my_ingress_headers_t *hdr, struct pna_global_metadata *compiler_meta__) +{ + struct hdr_md *hdrMd; + + unsigned ebpf_packetOffsetInBits_save = 0; + ParserError_t ebpf_errorCode = NoError; + void* pkt = ((void*)(long)skb->data); + u8* hdr_start = pkt; + void* ebpf_packetEnd = ((void*)(long)skb->data_end); + u32 ebpf_zero = 0; + u32 ebpf_one = 1; + unsigned char ebpf_byte; + u32 pkt_len = skb->len; + + struct my_ingress_metadata_t *meta; + + hdrMd = BPF_MAP_LOOKUP_ELEM(hdr_md_cpumap, &ebpf_zero); + if (!hdrMd) + return TC_ACT_SHOT; + __builtin_memset(hdrMd, 0, sizeof(struct hdr_md)); + + unsigned ebpf_packetOffsetInBits = 0; + hdr = &(hdrMd->cpumap_hdr); + meta = &(hdrMd->cpumap_usermeta); + { + goto start; + parse_arp: { +/* extract(hdr->arp) */ + if ((u8*)ebpf_packetEnd < hdr_start + BYTES(64 + 0)) { + ebpf_errorCode = PacketTooShort; + goto reject; + } + + hdr->arp.htype = (u16)((load_half(pkt, BYTES(ebpf_packetOffsetInBits)))); + ebpf_packetOffsetInBits += 16; + + hdr->arp.ptype = (u16)((load_half(pkt, BYTES(ebpf_packetOffsetInBits)))); + ebpf_packetOffsetInBits += 16; + + hdr->arp.hlen = (u8)((load_byte(pkt, BYTES(ebpf_packetOffsetInBits)))); + ebpf_packetOffsetInBits += 8; + + hdr->arp.plen = (u8)((load_byte(pkt, BYTES(ebpf_packetOffsetInBits)))); + ebpf_packetOffsetInBits += 8; + + hdr->arp.oper = (u16)((load_half(pkt, BYTES(ebpf_packetOffsetInBits)))); + ebpf_packetOffsetInBits += 16; + + + hdr->arp.ebpf_valid = 1; + hdr_start += BYTES(64); + +; +/* extract(hdr->arp_ipv4) */ + if ((u8*)ebpf_packetEnd < hdr_start + BYTES(160 + 0)) { + ebpf_errorCode = PacketTooShort; + goto reject; + } + + __builtin_memcpy(&hdr->arp_ipv4.sha, pkt + BYTES(ebpf_packetOffsetInBits), 6); + ebpf_packetOffsetInBits += 48; + + __builtin_memcpy(&hdr->arp_ipv4.spa, pkt + BYTES(ebpf_packetOffsetInBits), 4); + ebpf_packetOffsetInBits += 32; + + __builtin_memcpy(&hdr->arp_ipv4.tha, pkt + BYTES(ebpf_packetOffsetInBits), 6); + ebpf_packetOffsetInBits += 48; + + __builtin_memcpy(&hdr->arp_ipv4.tpa, pkt + BYTES(ebpf_packetOffsetInBits), 4); + ebpf_packetOffsetInBits += 32; + + + hdr->arp_ipv4.ebpf_valid = 1; + hdr_start += BYTES(160); + +; + u16 select_0; + select_0 = hdr->arp.oper; + if (select_0 == 1)goto accept; + if ((select_0 & 0x0) == (0x0 & 0x0))goto reject; + else goto reject; + } + start: { +/* extract(hdr->ethernet) */ + if ((u8*)ebpf_packetEnd < hdr_start + BYTES(112 + 0)) { + ebpf_errorCode = PacketTooShort; + goto reject; + } + + __builtin_memcpy(&hdr->ethernet.dstAddr, pkt + BYTES(ebpf_packetOffsetInBits), 6); + ebpf_packetOffsetInBits += 48; + + __builtin_memcpy(&hdr->ethernet.srcAddr, pkt + BYTES(ebpf_packetOffsetInBits), 6); + ebpf_packetOffsetInBits += 48; + + hdr->ethernet.etherType = (u16)((load_half(pkt, BYTES(ebpf_packetOffsetInBits)))); + ebpf_packetOffsetInBits += 16; + + + hdr->ethernet.ebpf_valid = 1; + hdr_start += BYTES(112); + +; + u16 select_1; + select_1 = hdr->ethernet.etherType; + if (select_1 == 0x806)goto parse_arp; + if ((select_1 & 0x0) == (0x0 & 0x0))goto accept; + else goto reject; + } + + reject: { + if (ebpf_errorCode == 0) { + return TC_ACT_SHOT; + } + compiler_meta__->parser_error = ebpf_errorCode; + goto accept; + } + + } + + accept: + hdrMd->ebpf_packetOffsetInBits = ebpf_packetOffsetInBits; + return -1; +} + +SEC("p4tc/parse") +int tc_parse_func(struct __sk_buff *skb) { + struct pna_global_metadata *compiler_meta__ = (struct pna_global_metadata *) skb->cb; + struct hdr_md *hdrMd; + struct my_ingress_headers_t *hdr; + int ret = -1; + ret = run_parser(skb, (struct my_ingress_headers_t *) hdr, compiler_meta__); + if (ret != -1) { + return ret; + } + return TC_ACT_PIPE; + } +char _license[] SEC("license") = "GPL"; diff --git a/testdata/p4tc_samples_stf_outputs/arp_respond_parser.h b/testdata/p4tc_samples_stf_outputs/arp_respond_parser.h new file mode 100644 index 00000000000..828a3209cea --- /dev/null +++ b/testdata/p4tc_samples_stf_outputs/arp_respond_parser.h @@ -0,0 +1,111 @@ +#include "ebpf_kernel.h" + +#include +#include +#include "pna.h" + +#define EBPF_MASK(t, w) ((((t)(1)) << (w)) - (t)1) +#define BYTES(w) ((w) / 8) +#define write_partial(a, w, s, v) do { *((u8*)a) = ((*((u8*)a)) & ~(EBPF_MASK(u8, w) << s)) | (v << s) ; } while (0) +#define write_byte(base, offset, v) do { *(u8*)((base) + (offset)) = (v); } while (0) +#define bpf_trace_message(fmt, ...) + + +struct my_ingress_metadata_t { +}; +struct empty_metadata_t { +}; +struct ethernet_t { + u8 dstAddr[6]; /* bit<48> */ + u8 srcAddr[6]; /* bit<48> */ + u16 etherType; /* bit<16> */ + u8 ebpf_valid; +}; +struct arp_t { + u16 htype; /* bit<16> */ + u16 ptype; /* bit<16> */ + u8 hlen; /* bit<8> */ + u8 plen; /* bit<8> */ + u16 oper; /* bit<16> */ + u8 ebpf_valid; +}; +struct arp_ipv4_t { + u8 sha[6]; /* bit<48> */ + u32 spa; /* bit<32> */ + u8 tha[6]; /* bit<48> */ + u32 tpa; /* bit<32> */ + u8 ebpf_valid; +}; +struct my_ingress_headers_t { + struct ethernet_t ethernet; /* ethernet_t */ + struct arp_t arp; /* arp_t */ + struct arp_ipv4_t arp_ipv4; /* arp_ipv4_t */ +}; + +struct hdr_md { + struct my_ingress_headers_t cpumap_hdr; + struct my_ingress_metadata_t cpumap_usermeta; + unsigned ebpf_packetOffsetInBits; + __u8 __hook; +}; + +struct p4tc_filter_fields { + __u32 pipeid; + __u32 handle; + __u32 classid; + __u32 chain; + __u32 blockid; + __be16 proto; + __u16 prio; +}; + +REGISTER_START() +REGISTER_TABLE(hdr_md_cpumap, BPF_MAP_TYPE_PERCPU_ARRAY, u32, struct hdr_md, 2) +BPF_ANNOTATE_KV_PAIR(hdr_md_cpumap, u32, struct hdr_md) +REGISTER_END() + +static inline u32 getPrimitive32(u8 *a, int size) { + if(size <= 16 || size > 24) { + bpf_printk("Invalid size."); + }; + return ((((u32)a[2]) <<16) | (((u32)a[1]) << 8) | a[0]); +} +static inline u64 getPrimitive64(u8 *a, int size) { + if(size <= 32 || size > 56) { + bpf_printk("Invalid size."); + }; + if(size <= 40) { + return ((((u64)a[4]) << 32) | (((u64)a[3]) << 24) | (((u64)a[2]) << 16) | (((u64)a[1]) << 8) | a[0]); + } else { + if(size <= 48) { + return ((((u64)a[5]) << 40) | (((u64)a[4]) << 32) | (((u64)a[3]) << 24) | (((u64)a[2]) << 16) | (((u64)a[1]) << 8) | a[0]); + } else { + return ((((u64)a[6]) << 48) | (((u64)a[5]) << 40) | (((u64)a[4]) << 32) | (((u64)a[3]) << 24) | (((u64)a[2]) << 16) | (((u64)a[1]) << 8) | a[0]); + } + } +} +static inline void storePrimitive32(u8 *a, int size, u32 value) { + if(size <= 16 || size > 24) { + bpf_printk("Invalid size."); + }; + a[0] = (u8)(value); + a[1] = (u8)(value >> 8); + a[2] = (u8)(value >> 16); +} +static inline void storePrimitive64(u8 *a, int size, u64 value) { + if(size <= 32 || size > 56) { + bpf_printk("Invalid size."); + }; + a[0] = (u8)(value); + a[1] = (u8)(value >> 8); + a[2] = (u8)(value >> 16); + a[3] = (u8)(value >> 24); + a[4] = (u8)(value >> 32); + if (size > 40) { + a[5] = (u8)(value >> 40); + } + if (size > 48) { + a[6] = (u8)(value >> 48); + } +} + diff --git a/testdata/p4tc_samples_stf_outputs/simple_l3.json b/testdata/p4tc_samples_stf_outputs/simple_l3.json new file mode 100644 index 00000000000..552f56ec2b8 --- /dev/null +++ b/testdata/p4tc_samples_stf_outputs/simple_l3.json @@ -0,0 +1,63 @@ +{ + "schema_version" : "1.0.0", + "pipeline_name" : "simple_l3", + "externs" : [], + "tables" : [ + { + "name" : "ingress/nh_table", + "id" : 1, + "tentries" : 2048, + "permissions" : "0x3da4", + "nummask" : 8, + "keysize" : 32, + "keyfields" : [ + { + "id" : 1, + "name" : "dstAddr", + "type" : "ipv4", + "match_type" : "exact", + "bitwidth" : 32 + } + ], + "actions" : [ + { + "id" : 1, + "name" : "ingress/send_nh", + "action_scope" : "TableAndDefault", + "annotations" : [], + "params" : [ + { + "id" : 1, + "name" : "port", + "type" : "dev", + "bitwidth" : 32 + }, + { + "id" : 2, + "name" : "srcMac", + "type" : "macaddr", + "bitwidth" : 48 + }, + { + "id" : 3, + "name" : "dstMac", + "type" : "macaddr", + "bitwidth" : 48 + } + ], + "default_hit_action" : false, + "default_miss_action" : false + }, + { + "id" : 2, + "name" : "ingress/drop", + "action_scope" : "TableAndDefault", + "annotations" : [], + "params" : [], + "default_hit_action" : false, + "default_miss_action" : true + } + ] + } + ] +} \ No newline at end of file diff --git a/testdata/p4tc_samples_stf_outputs/simple_l3.p4-stderr b/testdata/p4tc_samples_stf_outputs/simple_l3.p4-stderr new file mode 100644 index 00000000000..e69de29bb2d diff --git a/testdata/p4tc_samples_stf_outputs/simple_l3.template b/testdata/p4tc_samples_stf_outputs/simple_l3.template new file mode 100755 index 00000000000..ff5b7bed74e --- /dev/null +++ b/testdata/p4tc_samples_stf_outputs/simple_l3.template @@ -0,0 +1,24 @@ +#!/bin/bash -x + +set -e + +: "${TC:="tc"}" +$TC p4template create pipeline/simple_l3 numtables 1 + +$TC p4template create action/simple_l3/ingress/send_nh actid 1 \ + param port type dev \ + param srcMac type macaddr \ + param dstMac type macaddr +$TC p4template update action/simple_l3/ingress/send_nh state active + +$TC p4template create action/simple_l3/ingress/drop actid 2 +$TC p4template update action/simple_l3/ingress/drop state active + +$TC p4template create table/simple_l3/ingress/nh_table \ + tblid 1 \ + type exact \ + keysz 32 permissions 0x3da4 tentries 2048 nummasks 1 \ + table_acts act name simple_l3/ingress/send_nh \ + act name simple_l3/ingress/drop +$TC p4template update table/simple_l3/ingress/nh_table default_miss_action permissions 0x1024 action simple_l3/ingress/drop +$TC p4template update pipeline/simple_l3 state ready \ No newline at end of file diff --git a/testdata/p4tc_samples_stf_outputs/simple_l3_control_blocks.c b/testdata/p4tc_samples_stf_outputs/simple_l3_control_blocks.c new file mode 100644 index 00000000000..e3ec41509bf --- /dev/null +++ b/testdata/p4tc_samples_stf_outputs/simple_l3_control_blocks.c @@ -0,0 +1,322 @@ +#include "simple_l3_parser.h" +struct p4tc_filter_fields p4tc_filter_fields; + +struct internal_metadata { + __u16 pkt_ether_type; +} __attribute__((aligned(4))); + +struct skb_aggregate { + struct p4tc_skb_meta_get get; + struct p4tc_skb_meta_set set; +}; + +struct __attribute__((__packed__)) ingress_nh_table_key { + u32 keysz; + u32 maskid; + u32 field0; /* hdr.ipv4.dstAddr */ +} __attribute__((aligned(8))); +#define INGRESS_NH_TABLE_ACT_INGRESS_SEND_NH 1 +#define INGRESS_NH_TABLE_ACT_INGRESS_DROP 2 +#define INGRESS_NH_TABLE_ACT_NOACTION 0 +struct __attribute__((__packed__)) ingress_nh_table_value { + unsigned int action; + u32 hit:1, + is_default_miss_act:1, + is_default_hit_act:1; + union { + struct { + } _NoAction; + struct __attribute__((__packed__)) { + u32 port; + u8 srcMac[6]; + u8 dstMac[6]; + } ingress_send_nh; + struct { + } ingress_drop; + } u; +}; + +static __always_inline int process(struct __sk_buff *skb, struct my_ingress_headers_t *hdr, struct pna_global_metadata *compiler_meta__, struct skb_aggregate *sa) +{ + struct hdr_md *hdrMd; + + unsigned ebpf_packetOffsetInBits_save = 0; + ParserError_t ebpf_errorCode = NoError; + void* pkt = ((void*)(long)skb->data); + u8* hdr_start = pkt; + void* ebpf_packetEnd = ((void*)(long)skb->data_end); + u32 ebpf_zero = 0; + u32 ebpf_one = 1; + unsigned char ebpf_byte; + u32 pkt_len = skb->len; + + struct my_ingress_metadata_t *meta; + hdrMd = BPF_MAP_LOOKUP_ELEM(hdr_md_cpumap, &ebpf_zero); + if (!hdrMd) + return TC_ACT_SHOT; + unsigned ebpf_packetOffsetInBits = hdrMd->ebpf_packetOffsetInBits; + hdr_start = pkt + BYTES(ebpf_packetOffsetInBits); + hdr = &(hdrMd->cpumap_hdr); + meta = &(hdrMd->cpumap_usermeta); +{ + u8 hit; + { +if (/* hdr->ipv4.isValid() */ + hdr->ipv4.ebpf_valid && hdr->ipv4.protocol == 0x6) { +/* nh_table_0.apply() */ + { + /* construct key */ + struct p4tc_table_entry_act_bpf_params__local params = { + .pipeid = p4tc_filter_fields.pipeid, + .tblid = 1 + }; + struct ingress_nh_table_key key; + __builtin_memset(&key, 0, sizeof(key)); + key.keysz = 32; + key.field0 = hdr->ipv4.dstAddr; + struct p4tc_table_entry_act_bpf *act_bpf; + /* value */ + struct ingress_nh_table_value *value = NULL; + /* perform lookup */ + act_bpf = bpf_p4tc_tbl_read(skb, ¶ms, sizeof(params), &key, sizeof(key)); + value = (struct ingress_nh_table_value *)act_bpf; + if (value == NULL) { + /* miss; find default action */ + hit = 0; + } else { + hit = value->hit; + } + if (value != NULL) { + /* run action */ + switch (value->action) { + case INGRESS_NH_TABLE_ACT_INGRESS_SEND_NH: + { + storePrimitive64((u8 *)&hdr->ethernet.srcAddr, 48, (getPrimitive64((u8 *)value->u.ingress_send_nh.srcMac, 48))); + storePrimitive64((u8 *)&hdr->ethernet.dstAddr, 48, (getPrimitive64((u8 *)value->u.ingress_send_nh.dstMac, 48))); + /* send_to_port(value->u.ingress_send_nh.port) */ + compiler_meta__->drop = false; + send_to_port(value->u.ingress_send_nh.port); + } + break; + case INGRESS_NH_TABLE_ACT_INGRESS_DROP: + { +/* drop_packet() */ + drop_packet(); + } + break; + case INGRESS_NH_TABLE_ACT_NOACTION: + { + } + break; + } + } else { + } + } +; } + + } + } + { +{ +; + ; + } + + if (compiler_meta__->drop) { + return TC_ACT_SHOT; + } + int outHeaderLength = 0; + if (hdr->ethernet.ebpf_valid) { + outHeaderLength += 112; + } +; if (hdr->ipv4.ebpf_valid) { + outHeaderLength += 160; + } +; + __u16 saved_proto = 0; + bool have_saved_proto = false; + // bpf_skb_adjust_room works only when protocol is IPv4 or IPv6 + // 0x0800 = IPv4, 0x86dd = IPv6 + if ((skb->protocol != bpf_htons(0x0800)) && (skb->protocol != bpf_htons(0x86dd))) { + saved_proto = skb->protocol; + have_saved_proto = true; + bpf_p4tc_skb_set_protocol(skb, &sa->set, bpf_htons(0x0800)); + bpf_p4tc_skb_meta_set(skb, &sa->set, sizeof(sa->set)); + } + ; + + int outHeaderOffset = BYTES(outHeaderLength) - (hdr_start - (u8*)pkt); + if (outHeaderOffset != 0) { + int returnCode = 0; + returnCode = bpf_skb_adjust_room(skb, outHeaderOffset, 1, 0); + if (returnCode) { + return TC_ACT_SHOT; + } + } + + if (have_saved_proto) { + bpf_p4tc_skb_set_protocol(skb, &sa->set, saved_proto); + bpf_p4tc_skb_meta_set(skb, &sa->set, sizeof(sa->set)); + } + + pkt = ((void*)(long)skb->data); + ebpf_packetEnd = ((void*)(long)skb->data_end); + ebpf_packetOffsetInBits = 0; + if (hdr->ethernet.ebpf_valid) { + if (ebpf_packetEnd < pkt + BYTES(ebpf_packetOffsetInBits + 112)) { + return TC_ACT_SHOT; + } + + ebpf_byte = ((char*)(&hdr->ethernet.dstAddr))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.dstAddr))[1]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 1, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.dstAddr))[2]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 2, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.dstAddr))[3]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 3, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.dstAddr))[4]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 4, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.dstAddr))[5]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 5, (ebpf_byte)); + ebpf_packetOffsetInBits += 48; + + ebpf_byte = ((char*)(&hdr->ethernet.srcAddr))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.srcAddr))[1]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 1, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.srcAddr))[2]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 2, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.srcAddr))[3]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 3, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.srcAddr))[4]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 4, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.srcAddr))[5]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 5, (ebpf_byte)); + ebpf_packetOffsetInBits += 48; + + hdr->ethernet.etherType = bpf_htons(hdr->ethernet.etherType); + ebpf_byte = ((char*)(&hdr->ethernet.etherType))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ethernet.etherType))[1]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 1, (ebpf_byte)); + ebpf_packetOffsetInBits += 16; + + } +; if (hdr->ipv4.ebpf_valid) { + if (ebpf_packetEnd < pkt + BYTES(ebpf_packetOffsetInBits + 160)) { + return TC_ACT_SHOT; + } + + ebpf_byte = ((char*)(&hdr->ipv4.version))[0]; + write_partial(pkt + BYTES(ebpf_packetOffsetInBits) + 0, 4, 4, (ebpf_byte >> 0)); + ebpf_packetOffsetInBits += 4; + + ebpf_byte = ((char*)(&hdr->ipv4.ihl))[0]; + write_partial(pkt + BYTES(ebpf_packetOffsetInBits) + 0, 4, 0, (ebpf_byte >> 0)); + ebpf_packetOffsetInBits += 4; + + ebpf_byte = ((char*)(&hdr->ipv4.diffserv))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_packetOffsetInBits += 8; + + hdr->ipv4.totalLen = bpf_htons(hdr->ipv4.totalLen); + ebpf_byte = ((char*)(&hdr->ipv4.totalLen))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ipv4.totalLen))[1]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 1, (ebpf_byte)); + ebpf_packetOffsetInBits += 16; + + hdr->ipv4.identification = bpf_htons(hdr->ipv4.identification); + ebpf_byte = ((char*)(&hdr->ipv4.identification))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ipv4.identification))[1]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 1, (ebpf_byte)); + ebpf_packetOffsetInBits += 16; + + ebpf_byte = ((char*)(&hdr->ipv4.flags))[0]; + write_partial(pkt + BYTES(ebpf_packetOffsetInBits) + 0, 3, 5, (ebpf_byte >> 0)); + ebpf_packetOffsetInBits += 3; + + hdr->ipv4.fragOffset = bpf_htons(hdr->ipv4.fragOffset << 3); + ebpf_byte = ((char*)(&hdr->ipv4.fragOffset))[0]; + write_partial(pkt + BYTES(ebpf_packetOffsetInBits) + 0, 5, 0, (ebpf_byte >> 3)); + write_partial(pkt + BYTES(ebpf_packetOffsetInBits) + 0 + 1, 3, 5, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ipv4.fragOffset))[1]; + write_partial(pkt + BYTES(ebpf_packetOffsetInBits) + 1, 5, 0, (ebpf_byte >> 3)); + ebpf_packetOffsetInBits += 13; + + ebpf_byte = ((char*)(&hdr->ipv4.ttl))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_packetOffsetInBits += 8; + + ebpf_byte = ((char*)(&hdr->ipv4.protocol))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_packetOffsetInBits += 8; + + hdr->ipv4.hdrChecksum = bpf_htons(hdr->ipv4.hdrChecksum); + ebpf_byte = ((char*)(&hdr->ipv4.hdrChecksum))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ipv4.hdrChecksum))[1]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 1, (ebpf_byte)); + ebpf_packetOffsetInBits += 16; + + ebpf_byte = ((char*)(&hdr->ipv4.srcAddr))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ipv4.srcAddr))[1]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 1, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ipv4.srcAddr))[2]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 2, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ipv4.srcAddr))[3]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 3, (ebpf_byte)); + ebpf_packetOffsetInBits += 32; + + ebpf_byte = ((char*)(&hdr->ipv4.dstAddr))[0]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 0, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ipv4.dstAddr))[1]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 1, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ipv4.dstAddr))[2]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 2, (ebpf_byte)); + ebpf_byte = ((char*)(&hdr->ipv4.dstAddr))[3]; + write_byte(pkt, BYTES(ebpf_packetOffsetInBits) + 3, (ebpf_byte)); + ebpf_packetOffsetInBits += 32; + + } +; + } + return -1; +} +SEC("p4tc/main") +int tc_ingress_func(struct __sk_buff *skb) { + struct skb_aggregate skbstuff; + struct pna_global_metadata *compiler_meta__ = (struct pna_global_metadata *) skb->cb; + compiler_meta__->drop = false; + compiler_meta__->recirculate = false; + compiler_meta__->egress_port = 0; + if (!compiler_meta__->recirculated) { + compiler_meta__->mark = 153; + struct internal_metadata *md = (struct internal_metadata *)(unsigned long)skb->data_meta; + if ((void *) ((struct internal_metadata *) md + 1) <= (void *)(long)skb->data) { + __u16 *ether_type = (__u16 *) ((void *) (long)skb->data + 12); + if ((void *) ((__u16 *) ether_type + 1) > (void *) (long) skb->data_end) { + return TC_ACT_SHOT; + } + *ether_type = md->pkt_ether_type; + } + } + struct hdr_md *hdrMd; + struct my_ingress_headers_t *hdr; + int ret = -1; + ret = process(skb, (struct my_ingress_headers_t *) hdr, compiler_meta__, &skbstuff); + if (ret != -1) { + return ret; + } + if (!compiler_meta__->drop && compiler_meta__->recirculate) { + compiler_meta__->recirculated = true; + return TC_ACT_UNSPEC; + } + if (!compiler_meta__->drop && compiler_meta__->egress_port == 0) + return TC_ACT_OK; + return bpf_redirect(compiler_meta__->egress_port, 0); +} +char _license[] SEC("license") = "GPL"; diff --git a/testdata/p4tc_samples_stf_outputs/simple_l3_parser.c b/testdata/p4tc_samples_stf_outputs/simple_l3_parser.c new file mode 100644 index 00000000000..16213b60c85 --- /dev/null +++ b/testdata/p4tc_samples_stf_outputs/simple_l3_parser.c @@ -0,0 +1,140 @@ +#include "simple_l3_parser.h" + +struct p4tc_filter_fields p4tc_filter_fields; + +static __always_inline int run_parser(struct __sk_buff *skb, struct my_ingress_headers_t *hdr, struct pna_global_metadata *compiler_meta__) +{ + struct hdr_md *hdrMd; + + unsigned ebpf_packetOffsetInBits_save = 0; + ParserError_t ebpf_errorCode = NoError; + void* pkt = ((void*)(long)skb->data); + u8* hdr_start = pkt; + void* ebpf_packetEnd = ((void*)(long)skb->data_end); + u32 ebpf_zero = 0; + u32 ebpf_one = 1; + unsigned char ebpf_byte; + u32 pkt_len = skb->len; + + struct my_ingress_metadata_t *meta; + + hdrMd = BPF_MAP_LOOKUP_ELEM(hdr_md_cpumap, &ebpf_zero); + if (!hdrMd) + return TC_ACT_SHOT; + __builtin_memset(hdrMd, 0, sizeof(struct hdr_md)); + + unsigned ebpf_packetOffsetInBits = 0; + hdr = &(hdrMd->cpumap_hdr); + meta = &(hdrMd->cpumap_usermeta); + { + goto start; + parse_ipv4: { +/* extract(hdr->ipv4) */ + if ((u8*)ebpf_packetEnd < hdr_start + BYTES(160 + 0)) { + ebpf_errorCode = PacketTooShort; + goto reject; + } + + hdr->ipv4.version = (u8)((load_byte(pkt, BYTES(ebpf_packetOffsetInBits)) >> 4) & EBPF_MASK(u8, 4)); + ebpf_packetOffsetInBits += 4; + + hdr->ipv4.ihl = (u8)((load_byte(pkt, BYTES(ebpf_packetOffsetInBits))) & EBPF_MASK(u8, 4)); + ebpf_packetOffsetInBits += 4; + + hdr->ipv4.diffserv = (u8)((load_byte(pkt, BYTES(ebpf_packetOffsetInBits)))); + ebpf_packetOffsetInBits += 8; + + hdr->ipv4.totalLen = (u16)((load_half(pkt, BYTES(ebpf_packetOffsetInBits)))); + ebpf_packetOffsetInBits += 16; + + hdr->ipv4.identification = (u16)((load_half(pkt, BYTES(ebpf_packetOffsetInBits)))); + ebpf_packetOffsetInBits += 16; + + hdr->ipv4.flags = (u8)((load_byte(pkt, BYTES(ebpf_packetOffsetInBits)) >> 5) & EBPF_MASK(u8, 3)); + ebpf_packetOffsetInBits += 3; + + hdr->ipv4.fragOffset = (u16)((load_half(pkt, BYTES(ebpf_packetOffsetInBits))) & EBPF_MASK(u16, 13)); + ebpf_packetOffsetInBits += 13; + + hdr->ipv4.ttl = (u8)((load_byte(pkt, BYTES(ebpf_packetOffsetInBits)))); + ebpf_packetOffsetInBits += 8; + + hdr->ipv4.protocol = (u8)((load_byte(pkt, BYTES(ebpf_packetOffsetInBits)))); + ebpf_packetOffsetInBits += 8; + + hdr->ipv4.hdrChecksum = (u16)((load_half(pkt, BYTES(ebpf_packetOffsetInBits)))); + ebpf_packetOffsetInBits += 16; + + __builtin_memcpy(&hdr->ipv4.srcAddr, pkt + BYTES(ebpf_packetOffsetInBits), 4); + ebpf_packetOffsetInBits += 32; + + __builtin_memcpy(&hdr->ipv4.dstAddr, pkt + BYTES(ebpf_packetOffsetInBits), 4); + ebpf_packetOffsetInBits += 32; + + + hdr->ipv4.ebpf_valid = 1; + hdr_start += BYTES(160); + +; + u8 select_0; + select_0 = hdr->ipv4.protocol; + if (select_0 == 0x6)goto accept; + if ((select_0 & 0x0) == (0x0 & 0x0))goto reject; + else goto reject; + } + start: { +/* extract(hdr->ethernet) */ + if ((u8*)ebpf_packetEnd < hdr_start + BYTES(112 + 0)) { + ebpf_errorCode = PacketTooShort; + goto reject; + } + + __builtin_memcpy(&hdr->ethernet.dstAddr, pkt + BYTES(ebpf_packetOffsetInBits), 6); + ebpf_packetOffsetInBits += 48; + + __builtin_memcpy(&hdr->ethernet.srcAddr, pkt + BYTES(ebpf_packetOffsetInBits), 6); + ebpf_packetOffsetInBits += 48; + + hdr->ethernet.etherType = (u16)((load_half(pkt, BYTES(ebpf_packetOffsetInBits)))); + ebpf_packetOffsetInBits += 16; + + + hdr->ethernet.ebpf_valid = 1; + hdr_start += BYTES(112); + +; + u16 select_1; + select_1 = hdr->ethernet.etherType; + if (select_1 == 0x800)goto parse_ipv4; + if ((select_1 & 0x0) == (0x0 & 0x0))goto reject; + else goto reject; + } + + reject: { + if (ebpf_errorCode == 0) { + return TC_ACT_SHOT; + } + compiler_meta__->parser_error = ebpf_errorCode; + goto accept; + } + + } + + accept: + hdrMd->ebpf_packetOffsetInBits = ebpf_packetOffsetInBits; + return -1; +} + +SEC("p4tc/parse") +int tc_parse_func(struct __sk_buff *skb) { + struct pna_global_metadata *compiler_meta__ = (struct pna_global_metadata *) skb->cb; + struct hdr_md *hdrMd; + struct my_ingress_headers_t *hdr; + int ret = -1; + ret = run_parser(skb, (struct my_ingress_headers_t *) hdr, compiler_meta__); + if (ret != -1) { + return ret; + } + return TC_ACT_PIPE; + } +char _license[] SEC("license") = "GPL"; diff --git a/testdata/p4tc_samples_stf_outputs/simple_l3_parser.h b/testdata/p4tc_samples_stf_outputs/simple_l3_parser.h new file mode 100644 index 00000000000..6c090cc6152 --- /dev/null +++ b/testdata/p4tc_samples_stf_outputs/simple_l3_parser.h @@ -0,0 +1,110 @@ +#include "ebpf_kernel.h" + +#include +#include +#include "pna.h" + +#define EBPF_MASK(t, w) ((((t)(1)) << (w)) - (t)1) +#define BYTES(w) ((w) / 8) +#define write_partial(a, w, s, v) do { *((u8*)a) = ((*((u8*)a)) & ~(EBPF_MASK(u8, w) << s)) | (v << s) ; } while (0) +#define write_byte(base, offset, v) do { *(u8*)((base) + (offset)) = (v); } while (0) +#define bpf_trace_message(fmt, ...) + + +struct my_ingress_metadata_t { +}; +struct empty_metadata_t { +}; +struct ethernet_t { + u8 dstAddr[6]; /* bit<48> */ + u8 srcAddr[6]; /* bit<48> */ + u16 etherType; /* bit<16> */ + u8 ebpf_valid; +}; +struct ipv4_t { + u8 version; /* bit<4> */ + u8 ihl; /* bit<4> */ + u8 diffserv; /* bit<8> */ + u16 totalLen; /* bit<16> */ + u16 identification; /* bit<16> */ + u8 flags; /* bit<3> */ + u16 fragOffset; /* bit<13> */ + u8 ttl; /* bit<8> */ + u8 protocol; /* bit<8> */ + u16 hdrChecksum; /* bit<16> */ + u32 srcAddr; /* bit<32> */ + u32 dstAddr; /* bit<32> */ + u8 ebpf_valid; +}; +struct my_ingress_headers_t { + struct ethernet_t ethernet; /* ethernet_t */ + struct ipv4_t ipv4; /* ipv4_t */ +}; + +struct hdr_md { + struct my_ingress_headers_t cpumap_hdr; + struct my_ingress_metadata_t cpumap_usermeta; + unsigned ebpf_packetOffsetInBits; + __u8 __hook; +}; + +struct p4tc_filter_fields { + __u32 pipeid; + __u32 handle; + __u32 classid; + __u32 chain; + __u32 blockid; + __be16 proto; + __u16 prio; +}; + +REGISTER_START() +REGISTER_TABLE(hdr_md_cpumap, BPF_MAP_TYPE_PERCPU_ARRAY, u32, struct hdr_md, 2) +BPF_ANNOTATE_KV_PAIR(hdr_md_cpumap, u32, struct hdr_md) +REGISTER_END() + +static inline u32 getPrimitive32(u8 *a, int size) { + if(size <= 16 || size > 24) { + bpf_printk("Invalid size."); + }; + return ((((u32)a[2]) <<16) | (((u32)a[1]) << 8) | a[0]); +} +static inline u64 getPrimitive64(u8 *a, int size) { + if(size <= 32 || size > 56) { + bpf_printk("Invalid size."); + }; + if(size <= 40) { + return ((((u64)a[4]) << 32) | (((u64)a[3]) << 24) | (((u64)a[2]) << 16) | (((u64)a[1]) << 8) | a[0]); + } else { + if(size <= 48) { + return ((((u64)a[5]) << 40) | (((u64)a[4]) << 32) | (((u64)a[3]) << 24) | (((u64)a[2]) << 16) | (((u64)a[1]) << 8) | a[0]); + } else { + return ((((u64)a[6]) << 48) | (((u64)a[5]) << 40) | (((u64)a[4]) << 32) | (((u64)a[3]) << 24) | (((u64)a[2]) << 16) | (((u64)a[1]) << 8) | a[0]); + } + } +} +static inline void storePrimitive32(u8 *a, int size, u32 value) { + if(size <= 16 || size > 24) { + bpf_printk("Invalid size."); + }; + a[0] = (u8)(value); + a[1] = (u8)(value >> 8); + a[2] = (u8)(value >> 16); +} +static inline void storePrimitive64(u8 *a, int size, u64 value) { + if(size <= 32 || size > 56) { + bpf_printk("Invalid size."); + }; + a[0] = (u8)(value); + a[1] = (u8)(value >> 8); + a[2] = (u8)(value >> 16); + a[3] = (u8)(value >> 24); + a[4] = (u8)(value >> 32); + if (size > 40) { + a[5] = (u8)(value >> 40); + } + if (size > 48) { + a[6] = (u8)(value >> 48); + } +} + diff --git a/tools/ci-build.sh b/tools/ci-build.sh index 40549580f2d..686797f16d6 100755 --- a/tools/ci-build.sh +++ b/tools/ci-build.sh @@ -50,6 +50,8 @@ P4C_DIR=$(readlink -f ${THIS_DIR}/..) : "${ENABLE_BMV2:=ON}" # eBPF is enabled by default. : "${ENABLE_EBPF:=ON}" +# P4TC is enabled by default. +: "${ENABLE_P4TC:=ON}" # This is the list of back ends that can be enabled. # Back ends can be enabled from the command line with "ENABLE_[backend]=TRUE/FALSE" ENABLE_BACKENDS=("TOFINO" "BMV2" "EBPF" "UBPF" "DPDK" @@ -213,6 +215,48 @@ if [[ "${ENABLE_EBPF}" == "ON" ]] ; then fi # ! ------ END EBPF ----------------------------------------------- +# ! ------ BEGIN P4TC ----------------------------------------------- +function build_p4tc() { + P4TC_DEPS="libpcap-dev \ + libelf-dev \ + zlib1g-dev \ + gcc-multilib \ + net-tools \ + flex \ + libelf-dev \ + libmnl-dev \ + pkg-config \ + xtables-addons-source \ + bridge-utils \ + python3 \ + python3-pip \ + python3-venv \ + python3-argcomplete \ + wget \ + qemu qemu-system-x86" + + sudo apt-get install -y --no-install-recommends ${P4TC_DEPS} + + wget https://apt.llvm.org/llvm.sh + sudo chmod +x llvm.sh + sudo ./llvm.sh 15 + rm llvm.sh + + git clone --recurse-submodules https://github.com/arighi/virtme-ng.git ${P4C_DIR}/backends/tc/runtime/virtme-ng + pushd ${P4C_DIR}/backends/tc/runtime/virtme-ng + git checkout v1.19 + python3 -m venv ${P4C_DIR}/backends/tc/runtime/virtme-ng + source ${P4C_DIR}/backends/tc/runtime/virtme-ng/bin/activate + pip install --upgrade pip + pip install . + deactivate + popd +} +if [[ "${ENABLE_P4TC}" == "ON" ]] ; then + build_p4tc +fi +# ! ------ END P4TC ----------------------------------------------- + # ! ------ BEGIN DPDK ----------------------------------------------- function build_dpdk() { # Replace existing Protobuf with one that works. diff --git a/tools/install_fedora_deps.sh b/tools/install_fedora_deps.sh index 0788268af09..96741eb17a6 100755 --- a/tools/install_fedora_deps.sh +++ b/tools/install_fedora_deps.sh @@ -26,7 +26,7 @@ sudo dnf install -y -q \ boost-test \ boost-thread \ ccache \ - clang \ + clang-15 \ cmake \ cpp \ elfutils-libelf-devel \ @@ -57,6 +57,7 @@ sudo dnf install -y -q \ thrift-devel \ valgrind \ zlib-devel \ + glibc-devel.i686 \ ninja-build pip3 install --upgrade pip @@ -87,4 +88,5 @@ make -j$((`nproc`+1)) make -j$((`nproc`+1)) install-strip popd + rm -rf "${tmp_dir}" diff --git a/tools/ir-generator/ir-generator.ypp b/tools/ir-generator/ir-generator.ypp index 893cf262906..3dd060bccbc 100644 --- a/tools/ir-generator/ir-generator.ypp +++ b/tools/ir-generator/ir-generator.ypp @@ -253,7 +253,8 @@ method { $$ = new IrMethod(@2, canon_name($2)); } | modifiers IDENTIFIER '(' { if ($2 != $0->name) - yyerror("constructor name %s doesn't match class name %s", $2, $0->name); + yyerror("constructor name %s doesn't match class name %s", $2.c_str(), + $0->name.c_str()); if ($1 & ~IrField::Inline) yyerror("%s invalid on constructor", IrElement::modifier($1)); ($$ = new IrMethod(@2, $2))->inImpl = !($1 & IrField::Inline); @@ -274,7 +275,7 @@ method { ($$ = $5)->body = $10; if (!$10 || $10.startsWith("=")) { if (!$$->inImpl) - yyerror("inline method %v with no body", $3); + yyerror("inline method %v with no body", $3.c_str()); $$->inImpl = false; } $$->isUser = true; $$->isConst = $8; @@ -326,15 +327,15 @@ irField : modifiers type fieldName optInitializer ';' { $$ = new IrField(@3, $2, $3, $4, $1); if ($1 & IrElement::Virtual) - yyerror("virtual invalid on field %s", $3); } + yyerror("virtual invalid on field %s", $3.c_str()); } | modifiers CONST nonRefType fieldName optInitializer ';' { $$ = new IrField(@4, $3, $4, $5, $1 | IrElement::Const); if ($1 & IrElement::Virtual) - yyerror("virtual invalid on field %s", $4); } + yyerror("virtual invalid on field %s", $4.c_str()); } | modifiers VARIANT '<' type_args '>' fieldName optInitializer ';' { $$ = new IrVariantField(@6, $4, $6, $7, $1); if ($1 & IrElement::Virtual) - yyerror("virtual invalid on field %s", $6); } + yyerror("virtual invalid on field %s", $6.c_str()); } ; modifier diff --git a/tools/ir-generator/irclass.cpp b/tools/ir-generator/irclass.cpp index 013ca5d48dd..581205fa37f 100644 --- a/tools/ir-generator/irclass.cpp +++ b/tools/ir-generator/irclass.cpp @@ -99,6 +99,37 @@ void exit_namespace(std::ostream &out, IrNamespace *ns) { //////////////////////////////////////////////////////////////////////////////////// +/* sort class definitions so defs come before uses */ +void IrDefinitions::toposort() { + std::vector sorted; + std::map classes; + + auto visit = [&](const IrClass *cl) -> void { + auto do_visit = [&](const auto &self, const IrClass *cl) -> void { + auto it = classes.find(cl); + if (it != classes.end()) { + auto *cl = it->second; + classes.erase(it); + self(self, cl->concreteParent); + for (auto *p : cl->parentClasses) self(self, p); + sorted.push_back(cl); + } + }; + do_visit(do_visit, cl); + }; + + for (auto *el : elements) + if (auto *cl = el->to()) classes.emplace(cl, cl); + + for (auto *el : elements) { + if (auto *cl = el->to()) + visit(cl); + else + sorted.push_back(el); + } + elements = std::move(sorted); +} + Util::Enumerator *IrDefinitions::getClasses() const { return Util::enumerate(elements)->as()->where( [](IrClass *e) { return e != nullptr; }); @@ -572,6 +603,8 @@ bool IrClass::shouldSkip(cstring feature) const { } void IrClass::resolve() { + if (resolved) return; + resolved = true; for (auto s : parents) { const IrClass *p = s->resolve(containedIn); if (p == nullptr) throw Util::CompilationError("Could not find class %1%", s); diff --git a/tools/ir-generator/irclass.h b/tools/ir-generator/irclass.h index 0588b07a4cf..d09ebb16360 100644 --- a/tools/ir-generator/irclass.h +++ b/tools/ir-generator/irclass.h @@ -263,6 +263,7 @@ class CommentBlock : public IrElement { // Represents a C++ class for an IR node. class IrClass : public IrElement { + bool resolved = false; std::vector parentClasses; std::vector parents; const IrClass *concreteParent; @@ -274,6 +275,7 @@ class IrClass : public IrElement { void generateMethods(); bool shouldSkip(cstring feature) const; bool hasNoDirective(cstring feature) const; + friend class IrDefinitions; public: const IrClass *getParent() const { @@ -373,6 +375,7 @@ class IrDefinitions { public: explicit IrDefinitions(std::vector classes) : elements(classes) {} + void toposort(); void resolve() { IrClass::nodeClass()->resolve(); IrClass::vectorClass()->resolve(); @@ -381,6 +384,7 @@ class IrDefinitions { IrClass::ideclaration()->resolve(); IrClass::indexedVectorClass()->resolve(); for (auto cls : *getClasses()) cls->resolve(); + toposort(); } void generate(std::ostream &t, std::ostream &out, std::ostream &impl) const; }; diff --git a/tools/ir-generator/type.cpp b/tools/ir-generator/type.cpp index b34320430f4..397954c1c5c 100644 --- a/tools/ir-generator/type.cpp +++ b/tools/ir-generator/type.cpp @@ -51,7 +51,9 @@ const IrClass *NamedType::resolve(const IrNamespace *in) const { if (!in) return nullptr; if (auto *found = in->lookupClass(name)) { foundin = in; - return (resolved = found); + resolved = found; + found->resolve(); + return resolved; } if (in->lookupOther(name)) { foundin = in; @@ -61,7 +63,9 @@ const IrClass *NamedType::resolve(const IrNamespace *in) const { while (in) { if (auto *found = in->lookupClass(name)) { foundin = in; - return (resolved = found); + resolved = found; + found->resolve(); + return resolved; } if (in->lookupOther(name)) { foundin = in;