From e17c4f7324d8fc5cc24ba8ee1db451666cd7ced3 Mon Sep 17 00:00:00 2001 From: Dave Marchevsky Date: Fri, 17 Dec 2021 03:59:26 -0500 Subject: [PATCH] Remove P4 language support. Remove support for compiling P4 programs (see #3682 for explanation). Signed-off-by: Dave Marchevsky --- src/cc/frontends/p4/README.md | 374 ------------- src/cc/frontends/p4/compiler/README.txt | 4 - .../p4/compiler/compilationException.py | 34 -- src/cc/frontends/p4/compiler/ebpfAction.py | 382 ------------- .../frontends/p4/compiler/ebpfConditional.py | 118 ---- src/cc/frontends/p4/compiler/ebpfCounter.py | 116 ---- src/cc/frontends/p4/compiler/ebpfDeparser.py | 173 ------ src/cc/frontends/p4/compiler/ebpfInstance.py | 87 --- src/cc/frontends/p4/compiler/ebpfParser.py | 427 --------------- src/cc/frontends/p4/compiler/ebpfProgram.py | 506 ------------------ .../frontends/p4/compiler/ebpfScalarType.py | 84 --- .../frontends/p4/compiler/ebpfStructType.py | 128 ----- src/cc/frontends/p4/compiler/ebpfTable.py | 404 -------------- src/cc/frontends/p4/compiler/ebpfType.py | 30 -- src/cc/frontends/p4/compiler/p4toEbpf.py | 110 ---- .../p4/compiler/programSerializer.py | 64 --- src/cc/frontends/p4/compiler/target.py | 171 ------ src/cc/frontends/p4/compiler/topoSorting.py | 89 --- src/cc/frontends/p4/compiler/typeFactory.py | 33 -- src/cc/frontends/p4/docs/README.md | 3 - src/cc/frontends/p4/scope.png | Bin 101072 -> 0 bytes src/cc/frontends/p4/test/README.txt | 16 - src/cc/frontends/p4/test/cleanup.sh | 11 - src/cc/frontends/p4/test/endToEndTest.py | 376 ------------- src/cc/frontends/p4/test/testP4toEbpf.py | 85 --- src/cc/frontends/p4/test/testoutputs/.empty | 0 .../p4/test/testprograms/arrayKey.p4 | 34 -- .../p4/test/testprograms/basic_routing.p4 | 231 -------- .../p4/test/testprograms/bitfields.p4 | 66 --- .../p4/test/testprograms/compositeArray.p4 | 46 -- .../p4/test/testprograms/compositeKey.p4 | 72 --- .../p4/test/testprograms/do_nothing.p4 | 36 -- .../frontends/p4/test/testprograms/simple.p4 | 74 --- 33 files changed, 4384 deletions(-) delete mode 100644 src/cc/frontends/p4/README.md delete mode 100644 src/cc/frontends/p4/compiler/README.txt delete mode 100644 src/cc/frontends/p4/compiler/compilationException.py delete mode 100644 src/cc/frontends/p4/compiler/ebpfAction.py delete mode 100644 src/cc/frontends/p4/compiler/ebpfConditional.py delete mode 100644 src/cc/frontends/p4/compiler/ebpfCounter.py delete mode 100644 src/cc/frontends/p4/compiler/ebpfDeparser.py delete mode 100644 src/cc/frontends/p4/compiler/ebpfInstance.py delete mode 100644 src/cc/frontends/p4/compiler/ebpfParser.py delete mode 100644 src/cc/frontends/p4/compiler/ebpfProgram.py delete mode 100644 src/cc/frontends/p4/compiler/ebpfScalarType.py delete mode 100644 src/cc/frontends/p4/compiler/ebpfStructType.py delete mode 100644 src/cc/frontends/p4/compiler/ebpfTable.py delete mode 100644 src/cc/frontends/p4/compiler/ebpfType.py delete mode 100755 src/cc/frontends/p4/compiler/p4toEbpf.py delete mode 100644 src/cc/frontends/p4/compiler/programSerializer.py delete mode 100644 src/cc/frontends/p4/compiler/target.py delete mode 100644 src/cc/frontends/p4/compiler/topoSorting.py delete mode 100644 src/cc/frontends/p4/compiler/typeFactory.py delete mode 100644 src/cc/frontends/p4/docs/README.md delete mode 100644 src/cc/frontends/p4/scope.png delete mode 100644 src/cc/frontends/p4/test/README.txt delete mode 100755 src/cc/frontends/p4/test/cleanup.sh delete mode 100755 src/cc/frontends/p4/test/endToEndTest.py delete mode 100755 src/cc/frontends/p4/test/testP4toEbpf.py delete mode 100644 src/cc/frontends/p4/test/testoutputs/.empty delete mode 100644 src/cc/frontends/p4/test/testprograms/arrayKey.p4 delete mode 100644 src/cc/frontends/p4/test/testprograms/basic_routing.p4 delete mode 100644 src/cc/frontends/p4/test/testprograms/bitfields.p4 delete mode 100644 src/cc/frontends/p4/test/testprograms/compositeArray.p4 delete mode 100644 src/cc/frontends/p4/test/testprograms/compositeKey.p4 delete mode 100644 src/cc/frontends/p4/test/testprograms/do_nothing.p4 delete mode 100644 src/cc/frontends/p4/test/testprograms/simple.p4 diff --git a/src/cc/frontends/p4/README.md b/src/cc/frontends/p4/README.md deleted file mode 100644 index 4c7b50e7..00000000 --- a/src/cc/frontends/p4/README.md +++ /dev/null @@ -1,374 +0,0 @@ -# Compiling P4 to EBPF - -Mihai Budiu - mbudiu@barefootnetworks.com - -September 22, 2015 - -## Abstract - -This document describes a prototype compiler that translates programs -written in the P4 programming languages to eBPF programs. The -translation is performed by generating programs written in a subset of -the C programming language, that are converted to EBPF using the BPF -Compiler Collection tools. - -The compiler code is licensed under an [Apache v2.0 license] -(http://www.apache.org/licenses/LICENSE-2.0.html). - -## Preliminaries - -In this section we give a brief overview of P4 and EBPF. A detailed -treatment of these topics is outside the scope of this text. - -### P4 - -P4 (http://p4.org) is a domain-specific programming language for -specifying the behavior of the dataplanes of network-forwarding -elements. The name of the programming language comes from the title -of a paper published in the proceedings of SIGCOMM Computer -Communications Review in 2014: -http://www.sigcomm.org/ccr/papers/2014/July/0000000.0000004: -"Programming Protocol-Independent Packet Processors". - -P4 itself is protocol-independent but allows programmers to express a -rich set of data plane behaviors and protocols. The core P4 -abstractions are: - -* Header definitions describe the format (the set of fields and their - sizes) of each header within a packet. - -* Parse graphs (finite-state machines) describe the permitted header - sequences within received packets. - -* Tables associate keys to actions. P4 tables generalize traditional - forwarding tables; they can be used to implement routing tables, - flow lookup tables, access-control lists, etc. - -* Actions describe how packet header fields and metadata are manipulated. - -* Match-action units stitch together tables and actions, and perform - the following sequence of operations: - - * Construct lookup keys from packet fields or computed metadata, - - * Use the constructed lookup key to index into tables, choosing an - action to execute, - - * Finally, execute the selected action. - -* Control flow is expressed as an imperative program describing the - data-dependent packet processing within a pipeline, including the - data-dependent sequence of match-action unit invocations. - -P4 programs describe the behavior of network-processing dataplanes. A -P4 program is designed to operate in concert with a separate *control -plane* program. The control plane is responsible for managing at -runtime the contents of the P4 tables. P4 cannot be used to specify -control-planes; however, a P4 program implicitly specifies the -interface between the data-plane and the control-plane. - -The P4 language is under active development; the current stable -version is 1.0.2 (see http://p4.org/spec); a reference implementation -of a compiler and associated tools is freely available using a Apache -2 open-source license (see http://p4.org/code). - -### EBPF - -#### Safe code - -EBPF is a acronym that stands for Extended Berkeley Packet Filters. -In essence EBPF is a low-level programming language (similar to -machine code); EBPF programs are traditionally executed by a virtual -machine that resides in the Linux kernel. EBPF programs can be -inserted and removed from a live kernel using dynamic code -instrumentation. The main feature of EBPF programs is their *static -safety*: prior to execution all EBPF programs have to be validated as -being safe, and unsafe programs cannot be executed. A safe program -provably cannot compromise the machine it is running on: - -* it can only access a restricted memory region (on the local stack) - -* it can run only for a limited amount of time; during execution it - cannot block, sleep or take any locks - -* it cannot use any kernel resources with the exception of a limited - set of kernel services which have been specifically whitelisted, - including operations to manipulate tables (described below) - -#### Kernel hooks - -EBPF programs are inserted into the kernel using *hooks*. There are -several types of hooks available: - -* any function entry point in the kernel can act as a hook; attaching - an EBPF program to a function `foo()` will cause the EBPF program to - execute every time some kernel thread executes `foo()`. - -* EBPF programs can also be attached using the Linux Traffic Control - (TC) subsystem, in the network packet processing datapath. Such - programs can be used as TC classifiers and actions. - -* EBPF programs can also be attached to sockets or network interfaces. - In this case they can be used for processing packets that flow - through the socket/interface. - -EBPF programs can be used for many purposes; the main use cases are -dynamic tracing and monitoring, and packet processing. We are mostly -interested in the latter use case in this document. - -#### EBPF Tables - -The EBPF runtime exposes a bi-directional kernel-userspace data -communication channel, called *tables* (also called maps in some EBPF -documents and code samples). EBPF tables are essentially key-value -stores, where keys and values are arbitrary fixed-size bitstrings. -The key width, value width and table size (maximum number of entries -that can be stored) are declared statically, at table creation time. - -In user-space tables handles are exposed as file descriptors. Both -user- and kernel-space programs can manipulate tables, by inserting, -deleting, looking up, modifying, and enumerating entries in a table. - -In kernel space the keys and values are exposed as pointers to the raw -underlying data stored in the table, whereas in user-space the -pointers point to copies of the data. - -#### Concurrency - -An important aspect to understand related to EBPF is the execution -model. An EBPF program is triggered by a kernel hook; multiple -instances of the same kernel hook can be running simultaneously on -different cores. - -Each table however has a single instances across all the cores. A -single table may be accessed simultaneously by multiple instances of -the same EBPF program running as separate kernel threads on different -cores. EBPF tables are native kernel objects, and access to the table -contents is protected using the kernel RCU mechanism. This makes -access to table entries safe under concurrent execution; for example, -the memory associated to a value cannot be accidentally freed while an -EBPF program holds a pointer to the respective value. However, -accessing tables is prone to data races; since EBPF programs cannot -use locks, some of these races often cannot be avoided. - -EBPF and the associated tools are also under active development, and -new capabilities are added frequently. The P4 compiler generates code -that can be compiled using the BPF Compiler Collection (BCC) -(https://github.com/iovisor/bcc) - -## Compiling P4 to EBPF - -From the above description it is apparent that the P4 and EBPF -programming languages have different expressive powers. However, -there is a significant overlap in their capabilities, in particular, -in the domain of network packet processing. The following image -illustrates the situation: - -![P4 and EBPF overlap in capabilities](scope.png) - -We expect that the overlapping region will grow in size as both P4 and -EBPF continue to mature. - -The current version of the P4 to EBPF compiler translates programs -written in the version 1.1 of the P4 programming language to programs -written in a restricted subset of C. The subset of C is chosen such -that it should be compilable to EBPF using BCC. - -``` - -------------- ------- -P4 ---> | P4-to-EBPF | ---> C ----> | BCC | --> EBPF - -------------- ------- -``` - -The P4 program only describes the packet processing *data plane*, that -runs in the Linux kernel. The *control plane* must be separately -implemented by the user. The BCC tools simplify this task -considerably, by generating C and/or Python APIs that expose the -dataplane/control-plane APIs. - -### Dependencies - -EBPF programs require a Linux kernel with version 4.2 or newer. - -In order to use the P4 to EBPF compiler the following software must be installed: - -* The compiler itself is written in the Python (v2.x) programming - language. - -* the P4 compiler front-end: (https://github.com/p4lang/p4-hlir). - This is required for parsing the P4 programs. - -* the BCC compiler collection tools: (https://github.com/iovisor/bcc). - This is required for compiling the generated code. Also, BCC comes - with a set of Python utilities which can be used to implement - control-plane programs that operate in concert with the kernel EBPF - datapath. - -The P4 to EBPF compiler generates code that is designed for being used -as a classifier using the Linux TC subsystem. - -Furthermore, the test code provided is written using the Python (v3.x) -programming language and requires several Python packages to be -installed. - -### Supported capabilities - -The current version of the P4 to EBPF compiler supports a relatively -narrow subset of the P4 language, but still powerful enough to write -very complex packet filters and simple packet forwarding engines. In -the spirit of open-source "release early, release often", we expect -that the compiler's capabilities will improve gradually. - -* Packet filtering is performed using the `drop()` action. Packets - that are not dropped will be forwarded. - -* Packet forwarding is performed by setting the - `standard_metadata.egress_port` to the index of the destination - network interface - -Here are some limitations imposed on the P4 programs: - -* Currently both the ingress and the egress P4 pipelines are executed - at the same hook (wherever the user chooses to insert the generated - EBPF program). In the future the compiler should probably generate - two separate EBPF programs. - -* arbitrary parsers can be compiled, but the BCC compiler will reject - parsers that contain cycles - -* arithmetic on data wider than 32 bits is not supported - -* checksum computations are not implemented. In consequence, programs - that IP/TCP/UDP headers will produce incorrect packet headers. - -* EBPF does not offer support for ternary or LPM tables - -* P4 cloning and recirculation and not supported - -* meters and registers are not supported; only direct counters are - currently supported. EBPF can potentially support registers and - arbitrary counters, so these may appear in the future. - -* learning (i.e. `generate_digest`) is not implemented - -### Translating P4 to C - -To simplify the translation, the P4 programmer should refrain using -identifiers whose name starts with `ebpf_`. - -The following table provides a brief summary of how each P4 construct -is mapped to a corresponding C construct: - -#### Translating parsers - -P4 Construct | C Translation -----------|------------ -`header_type` | `struct` type -`header` | `struct` instance with an additional `valid` bit -`metadata` | `struct` instance -parser state | code block -state transition | `goto` statement -`extract` | load/shift/mask data from packet buffer - -#### Translating match-action pipelines - -P4 Construct | C Translation -----------|------------ -table | 2 EBPF tables: second one used just for the default action -table key | `struct` type -table `actions` block | tagged `union` with all possible actions -`action` arguments | `struct` -table `reads` | EBPF table access -`action` body | code block -table `apply` | `switch` statement -counters | additional EBPF table - -### Code organization - -The compiler code is organized in two folders: - -* `compiler`: the complete compiler source code, in Python v2.x - The compiler entry point is `p4toEbpf.py`. - -* `test`: testing code and data. There are two testing programs: - * `testP4toEbpf.py`: which compiles all P4 files in the testprograms folder - - * `endToEndTest.py`: which compiles and executes the simple.p4 - program, and includes a simple control plane - -Currently the compiler contains no installation capabilities. - -### Invoking the compiler - -Invoking the compiler is just a matter of invoking the python program -with a suitable input P4 file: - -``` -p4toEbpf.py file.p4 -o file.c -``` - -#### Compiler options - -The P4 compiler first runs the C preprocessor on the input P4 file. -Some of the command-line options are passed directly to the -preprocessor. - -The following compiler options are available: - -Option | Meaning --------|-------- -`-D macro` | Option passed to C preprocessor -`-I path` | Option passed to C preprocessor -`-U macro` | Option passed to C preprocessor -`-g [router|filter]` | Controls whether the generated code behaves like a router or a filter. -`-o outoutFile` | writes the generated C code to the specified output file. - -The `-g` option controls the nature of the generated code: - -* `-g filter` generates a filter; the only P4 action that has an - effect is the `drop()` action. Setting metadata in P4 (e.g., - `egress_port`) has no effect. - -* `-g router` generates a simple router; both `drop()` and - `egress_port` impact packet processing. - -#### Using the generated code - -The resulting file contains the complete data structures, tables, and -a C function named `ebpf_filter` that implements the P4-specified -data-plane. This C file can be manipulated using the BCC tools; -please refer to the BCC project documentation and sample test files of -the P4 to EBPF source code for an in-depth understanding. A minimal -Python program that compiles and loads into the kernel the generated -file into EBPF is: - -``` -#!/usr/bin/env python3 -from bcc import BPF - -b = BPF(src_file="file.c", debug=0) -fn = b.load_func("ebpf_filter", BPF.SCHED_CLS) -``` - -##### Connecting the generated program with the TC - -The EBPF code that is generated is intended to be used as a classifier -attached to the ingress packet path using the Linux TC subsystem. The -same EBPF code should be attached to all interfaces. Note however -that all EBPF code instances share a single set of tables, which are -used to control the program behavior. - -The following code fragment illustrates how the EBPF code can be -hooked up to the `eth0` interface using a Python program. (The `fn` -variable is the one produced by the previous code fragment). - -``` -from pyroute2 import IPRoute - -ipr = IPRoute() -interface_name="eth0" -if_index = ipr.link_lookup(ifname=interface_name)[0] -ipr.tc("add", "ingress", if_index, "ffff:") -ipr.tc("add-filter", "bpf", if_index, ":1", fd=fn.fd, - name=fn.name, parent="ffff:", action="ok", classid=1) -``` diff --git a/src/cc/frontends/p4/compiler/README.txt b/src/cc/frontends/p4/compiler/README.txt deleted file mode 100644 index c6102405..00000000 --- a/src/cc/frontends/p4/compiler/README.txt +++ /dev/null @@ -1,4 +0,0 @@ -This folder contains an implementation of a simple compiler that -translates a programs written in a subset of P4 into C that can in -turn be compiled into EBPF using the IOVisor bcc compiler. - diff --git a/src/cc/frontends/p4/compiler/compilationException.py b/src/cc/frontends/p4/compiler/compilationException.py deleted file mode 100644 index cc0e5ba7..00000000 --- a/src/cc/frontends/p4/compiler/compilationException.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (c) Barefoot Networks, Inc. -# Licensed under the Apache License, Version 2.0 (the "License") - -class CompilationException(Exception): - """Signals an error during compilation""" - def __init__(self, isBug, format, *message): - # isBug: indicates that this is a compiler bug - super(CompilationException, self).__init__() - - assert isinstance(format, str) - assert isinstance(isBug, bool) - self.message = message - self.format = format - self.isBug = isBug - - def show(self): - # TODO: format this message nicely - return self.format.format(*self.message) - - -class NotSupportedException(Exception): - archError = " not supported by EBPF" - - def __init__(self, format, *message): - super(NotSupportedException, self).__init__() - - assert isinstance(format, str) - self.message = message - self.format = format - - def show(self): - # TODO: format this message nicely - return (self.format + NotSupportedException.archError).format( - *self.message) diff --git a/src/cc/frontends/p4/compiler/ebpfAction.py b/src/cc/frontends/p4/compiler/ebpfAction.py deleted file mode 100644 index 99bf1455..00000000 --- a/src/cc/frontends/p4/compiler/ebpfAction.py +++ /dev/null @@ -1,382 +0,0 @@ -# Copyright (c) Barefoot Networks, Inc. -# Licensed under the Apache License, Version 2.0 (the "License") - -from p4_hlir.hlir import p4_action, p4_field -from p4_hlir.hlir import p4_signature_ref, p4_header_instance -import ebpfProgram -from programSerializer import ProgramSerializer -from compilationException import * -import ebpfScalarType -import ebpfCounter -import ebpfType -import ebpfInstance - - -class EbpfActionData(object): - def __init__(self, name, argtype): - self.name = name - self.argtype = argtype - - -class EbpfActionBase(object): - def __init__(self, p4action): - self.name = p4action.name - self.hliraction = p4action - self.builtin = False - self.arguments = [] - - def serializeArgumentsAsStruct(self, serializer): - serializer.emitIndent() - serializer.appendFormat("/* no arguments for {0} */", self.name) - serializer.newline() - - def serializeBody(self, serializer, valueName, program): - serializer.emitIndent() - serializer.appendFormat("/* no body for {0} */", self.name) - serializer.newline() - - def __str__(self): - return "EbpfAction({0})".format(self.name) - - -class EbpfAction(EbpfActionBase): - unsupported = [ - # The following cannot be done in EBPF - "add_header", "remove_header", "execute_meter", - "clone_ingress_pkt_to_egress", - "clone_egress_pkt_to_egress", "generate_digest", "resubmit", - "modify_field_with_hash_based_offset", "truncate", "push", "pop", - # The following could be done, but are not yet implemented - # The situation with copy_header is complicated, - # because we don't do checksums - "copy_header", "count", - "register_read", "register_write"] - - # noinspection PyUnresolvedReferences - def __init__(self, p4action, program): - super(EbpfAction, self).__init__(p4action) - assert isinstance(p4action, p4_action) - assert isinstance(program, ebpfProgram.EbpfProgram) - - self.builtin = False - self.invalid = False # a leaf action which is never - # called from a table can be invalid. - - for i in range(0, len(p4action.signature)): - param = p4action.signature[i] - width = p4action.signature_widths[i] - if width is None: - self.invalid = True - return - argtype = ebpfScalarType.EbpfScalarType(p4action, width, - False, program.config) - actionData = EbpfActionData(param, argtype) - self.arguments.append(actionData) - - def serializeArgumentsAsStruct(self, serializer): - if self.invalid: - raise CompilationException(True, - "{0} Attempting to generate code for an invalid action", - self.hliraction) - - # Build a struct containing all action arguments. - serializer.emitIndent() - serializer.append("struct ") - serializer.blockStart() - assert isinstance(serializer, ProgramSerializer) - for arg in self.arguments: - assert isinstance(arg, EbpfActionData) - serializer.emitIndent() - argtype = arg.argtype - assert isinstance(argtype, ebpfType.EbpfType) - argtype.declare(serializer, arg.name, False) - serializer.endOfStatement(True) - serializer.blockEnd(False) - serializer.space() - serializer.append(self.name) - serializer.endOfStatement(True) - - def serializeBody(self, serializer, dataContainer, program): - if self.invalid: - raise CompilationException(True, - "{0} Attempting to generate code for an invalid action", - self.hliraction) - - # TODO: generate PARALLEL implementation - # dataContainer is a string containing the variable name - # containing the action data - assert isinstance(serializer, ProgramSerializer) - assert isinstance(program, ebpfProgram.EbpfProgram) - assert isinstance(dataContainer, str) - callee_list = self.hliraction.flat_call_sequence - for e in callee_list: - action = e[0] - assert isinstance(action, p4_action) - arguments = e[1] - assert isinstance(arguments, list) - self.serializeCallee(self, action, arguments, serializer, - dataContainer, program) - - def checkSize(self, call, args, program): - size = None - for a in args: - if a is None: - continue - if size is None: - size = a - elif a != size: - program.emitWarning( - "{0}: Arguments do not have the same size {1} and {2}", - call, size, a) - return size - - @staticmethod - def translateActionToOperator(actionName): - if actionName == "add" or actionName == "add_to_field": - return "+" - elif actionName == "bit_and": - return "&" - elif actionName == "bit_or": - return "|" - elif actionName == "bit_xor": - return "^" - elif actionName == "subtract" or actionName == "subtract_from_field": - return "-" - else: - raise CompilationException(True, - "Unexpected primitive action {0}", - actionName) - - def serializeCount(self, caller, arguments, serializer, - dataContainer, program): - assert isinstance(serializer, ProgramSerializer) - assert isinstance(program, ebpfProgram.EbpfProgram) - assert isinstance(arguments, list) - assert len(arguments) == 2 - - counter = arguments[0] - index = ArgInfo(arguments[1], caller, dataContainer, program) - ctr = program.getCounter(counter.name) - assert isinstance(ctr, ebpfCounter.EbpfCounter) - serializer.emitIndent() - serializer.blockStart() - - # This is actually incorrect, since the key is not always an u32. - # This code is currently disabled - key = program.reservedPrefix + "index" - serializer.emitIndent() - serializer.appendFormat("u32 {0} = {1};", key, index.asString) - serializer.newline() - - ctr.serializeCode(key, serializer, program) - - serializer.blockEnd(True) - - def serializeCallee(self, caller, callee, arguments, - serializer, dataContainer, program): - if self.invalid: - raise CompilationException( - True, - "{0} Attempting to generate code for an invalid action", - self.hliraction) - - assert isinstance(serializer, ProgramSerializer) - assert isinstance(program, ebpfProgram.EbpfProgram) - assert isinstance(callee, p4_action) - assert isinstance(arguments, list) - - if callee.name in EbpfAction.unsupported: - raise NotSupportedException("{0}", callee) - - # This is not yet ready - #if callee.name == "count": - # self.serializeCount(caller, arguments, - # serializer, dataContainer, program) - # return - - serializer.emitIndent() - args = self.transformArguments(arguments, caller, - dataContainer, program) - if callee.name == "modify_field": - dst = args[0] - src = args[1] - - size = self.checkSize(callee, - [a.widthInBits() for a in args], - program) - if size is None: - raise CompilationException( - True, "Cannot infer width for arguments {0}", - callee) - elif size <= 32: - serializer.appendFormat("{0} = {1};", - dst.asString, - src.asString) - else: - if not dst.isLvalue: - raise NotSupportedException( - "Constants wider than 32-bit: {0}({1})", - dst.caller, dst.asString) - if not src.isLvalue: - raise NotSupportedException( - "Constants wider than 32-bit: {0}({1})", - src.caller, src.asString) - serializer.appendFormat("memcpy(&{0}, &{1}, {2});", - dst.asString, - src.asString, - size / 8) - elif (callee.name == "add" or - callee.name == "bit_and" or - callee.name == "bit_or" or - callee.name == "bit_xor" or - callee.name == "subtract"): - size = self.checkSize(callee, - [a.widthInBits() for a in args], - program) - if size is None: - raise CompilationException( - True, - "Cannot infer width for arguments {0}", - callee) - if size > 32: - raise NotSupportedException("{0}: Arithmetic on {1}-bits", - callee, size) - op = EbpfAction.translateActionToOperator(callee.name) - serializer.appendFormat("{0} = {1} {2} {3};", - args[0].asString, - args[1].asString, - op, - args[2].asString) - elif (callee.name == "add_to_field" or - callee.name == "subtract_from_field"): - size = self.checkSize(callee, - [a.widthInBits() for a in args], - program) - if size is None: - raise CompilationException( - True, "Cannot infer width for arguments {0}", callee) - if size > 32: - raise NotSupportedException( - "{0}: Arithmetic on {1}-bits", callee, size) - - op = EbpfAction.translateActionToOperator(callee.name) - serializer.appendFormat("{0} = {0} {1} {2};", - args[0].asString, - op, - args[1].asString) - elif callee.name == "no_op": - serializer.append("/* noop */") - elif callee.name == "drop": - serializer.appendFormat("{0} = 1;", program.dropBit) - elif callee.name == "push" or callee.name == "pop": - raise CompilationException( - True, "{0} push/pop not yet implemented", callee) - else: - raise CompilationException( - True, "Unexpected primitive action {0}", callee) - serializer.newline() - - def transformArguments(self, arguments, caller, dataContainer, program): - result = [] - for a in arguments: - t = ArgInfo(a, caller, dataContainer, program) - result.append(t) - return result - - -class BuiltinAction(EbpfActionBase): - def __init__(self, p4action): - super(BuiltinAction, self).__init__(p4action) - self.builtin = True - - def serializeBody(self, serializer, valueName, program): - # This is ugly; there should be a better way - if self.name == "drop": - serializer.emitIndent() - serializer.appendFormat("{0} = 1;", program.dropBit) - serializer.newline() - else: - serializer.emitIndent() - serializer.appendFormat("/* no body for {0} */", self.name) - serializer.newline() - - -class ArgInfo(object): - # noinspection PyUnresolvedReferences - # Represents an argument passed to an action - def __init__(self, argument, caller, dataContainer, program): - self.width = None - self.asString = None - self.isLvalue = True - self.caller = caller - - assert isinstance(program, ebpfProgram.EbpfProgram) - assert isinstance(caller, EbpfAction) - - if isinstance(argument, int): - self.asString = str(argument) - self.isLvalue = False - # size is unknown - elif isinstance(argument, p4_field): - if ebpfProgram.EbpfProgram.isArrayElementInstance( - argument.instance): - if isinstance(argument.instance.index, int): - index = "[" + str(argument.instance.index) + "]" - else: - raise CompilationException( - True, - "Unexpected index for array {0}", - argument.instance.index) - stackInstance = program.getStackInstance( - argument.instance.base_name) - assert isinstance(stackInstance, ebpfInstance.EbpfHeaderStack) - fieldtype = stackInstance.basetype.getField(argument.name) - self.width = fieldtype.widthInBits() - self.asString = "{0}.{1}{3}.{2}".format( - program.headerStructName, - stackInstance.name, argument.name, index) - else: - instance = program.getInstance(argument.instance.base_name) - if isinstance(instance, ebpfInstance.EbpfHeader): - parent = program.headerStructName - else: - parent = program.metadataStructName - fieldtype = instance.type.getField(argument.name) - self.width = fieldtype.widthInBits() - self.asString = "{0}.{1}.{2}".format( - parent, instance.name, argument.name) - elif isinstance(argument, p4_signature_ref): - refarg = caller.arguments[argument.idx] - self.asString = "{0}->u.{1}.{2}".format( - dataContainer, caller.name, refarg.name) - self.width = caller.arguments[argument.idx].argtype.widthInBits() - elif isinstance(argument, p4_header_instance): - # This could be a header array element - # Unfortunately for push and pop, the user mean the whole array, - # but the representation contains just the first element here. - # This looks like a bug in the HLIR. - if ebpfProgram.EbpfProgram.isArrayElementInstance(argument): - if isinstance(argument.index, int): - index = "[" + str(argument.index) + "]" - else: - raise CompilationException( - True, - "Unexpected index for array {0}", argument.index) - stackInstance = program.getStackInstance(argument.base_name) - assert isinstance(stackInstance, ebpfInstance.EbpfHeaderStack) - fieldtype = stackInstance.basetype - self.width = fieldtype.widthInBits() - self.asString = "{0}.{1}{2}".format( - program.headerStructName, stackInstance.name, index) - else: - instance = program.getInstance(argument.name) - instancetype = instance.type - self.width = instancetype.widthInBits() - self.asString = "{0}.{1}".format( - program.headerStructName, argument.name) - else: - raise CompilationException( - True, "Unexpected action argument {0}", argument) - - def widthInBits(self): - return self.width diff --git a/src/cc/frontends/p4/compiler/ebpfConditional.py b/src/cc/frontends/p4/compiler/ebpfConditional.py deleted file mode 100644 index 5c723d23..00000000 --- a/src/cc/frontends/p4/compiler/ebpfConditional.py +++ /dev/null @@ -1,118 +0,0 @@ -# Copyright (c) Barefoot Networks, Inc. -# Licensed under the Apache License, Version 2.0 (the "License") - -from p4_hlir.hlir import p4_conditional_node, p4_expression -from p4_hlir.hlir import p4_header_instance, p4_field -from programSerializer import ProgramSerializer -from compilationException import CompilationException -import ebpfProgram -import ebpfInstance - - -class EbpfConditional(object): - @staticmethod - def translate(op): - if op == "not": - return "!" - elif op == "or": - return "||" - elif op == "and": - return "&&" - return op - - def __init__(self, p4conditional, program): - assert isinstance(p4conditional, p4_conditional_node) - assert isinstance(program, ebpfProgram.EbpfProgram) - self.hlirconditional = p4conditional - self.name = p4conditional.name - - def emitNode(self, node, serializer, program): - if isinstance(node, p4_expression): - self.emitExpression(node, serializer, program, False) - elif node is None: - pass - elif isinstance(node, int): - serializer.append(node) - elif isinstance(node, p4_header_instance): - header = program.getInstance(node.name) - assert isinstance(header, ebpfInstance.EbpfHeader) - # TODO: stacks? - serializer.appendFormat( - "{0}.{1}", program.headerStructName, header.name) - elif isinstance(node, p4_field): - instance = node.instance - einstance = program.getInstance(instance.name) - if isinstance(einstance, ebpfInstance.EbpfHeader): - base = program.headerStructName - else: - base = program.metadataStructName - serializer.appendFormat( - "{0}.{1}.{2}", base, einstance.name, node.name) - else: - raise CompilationException(True, "{0} Unexpected expression ", node) - - def emitExpression(self, expression, serializer, program, toplevel): - assert isinstance(serializer, ProgramSerializer) - assert isinstance(program, ebpfProgram.EbpfProgram) - assert isinstance(expression, p4_expression) - assert isinstance(toplevel, bool) - left = expression.left - op = expression.op - right = expression.right - - assert isinstance(op, str) - - if op == "valid": - self.emitNode(right, serializer, program) - serializer.append(".valid") - return - - if not toplevel: - serializer.append("(") - self.emitNode(left, serializer, program) - op = EbpfConditional.translate(op) - serializer.append(op) - self.emitNode(right, serializer, program) - if not toplevel: - serializer.append(")") - - def generateCode(self, serializer, program, nextNode): - assert isinstance(serializer, ProgramSerializer) - assert isinstance(program, ebpfProgram.EbpfProgram) - serializer.emitIndent() - serializer.blockStart() - - trueBranch = self.hlirconditional.next_[True] - if trueBranch is None: - trueBranch = nextNode - falseBranch = self.hlirconditional.next_[False] - if falseBranch is None: - falseBranch = nextNode - - serializer.emitIndent() - serializer.appendFormat("{0}:", program.getLabel(self.hlirconditional)) - serializer.newline() - - serializer.emitIndent() - serializer.append("if (") - self.emitExpression( - self.hlirconditional.condition, serializer, program, True) - serializer.appendLine(")") - - serializer.increaseIndent() - label = program.getLabel(trueBranch) - serializer.emitIndent() - serializer.appendFormat("goto {0};", label) - serializer.newline() - serializer.decreaseIndent() - - serializer.emitIndent() - serializer.appendLine("else") - serializer.increaseIndent() - label = program.getLabel(falseBranch) - serializer.emitIndent() - serializer.appendFormat("goto {0};", label) - serializer.newline() - serializer.decreaseIndent() - - serializer.blockEnd(True) diff --git a/src/cc/frontends/p4/compiler/ebpfCounter.py b/src/cc/frontends/p4/compiler/ebpfCounter.py deleted file mode 100644 index 5b5b3963..00000000 --- a/src/cc/frontends/p4/compiler/ebpfCounter.py +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright (c) Barefoot Networks, Inc. -# Licensed under the Apache License, Version 2.0 (the "License") - -from p4_hlir.hlir import p4_counter, P4_DIRECT, P4_COUNTER_BYTES -from programSerializer import ProgramSerializer -from compilationException import * -import ebpfTable -import ebpfProgram - - -class EbpfCounter(object): - # noinspection PyUnresolvedReferences - def __init__(self, hlircounter, program): - assert isinstance(hlircounter, p4_counter) - assert isinstance(program, ebpfProgram.EbpfProgram) - - self.name = hlircounter.name - self.hlircounter = hlircounter - - width = hlircounter.min_width - # ebpf counters only work on 64-bits - if width <= 64: - self.valueTypeName = program.config.uprefix + "64" - else: - raise NotSupportedException( - "{0}: Counters with {1} bits", hlircounter, width) - - self.dataMapName = self.name - - if ((hlircounter.binding is None) or - (hlircounter.binding[0] != P4_DIRECT)): - raise NotSupportedException( - "{0}: counter which is not direct", hlircounter) - - self.autoIncrement = (hlircounter.binding != None and - hlircounter.binding[0] == P4_DIRECT) - - if hlircounter.type is P4_COUNTER_BYTES: - self.increment = "{0}->len".format(program.packetName) - else: - self.increment = "1" - - def getSize(self, program): - if self.hlircounter.instance_count is not None: - return self.hlircounter.instance_count - if self.autoIncrement: - return self.getTable(program).size - program.emitWarning( - "{0} does not specify a max_size; using 1024", self.hlircounter) - return 1024 - - def getTable(self, program): - table = program.getTable(self.hlircounter.binding[1].name) - assert isinstance(table, ebpfTable.EbpfTable) - return table - - def serialize(self, serializer, program): - assert isinstance(serializer, ProgramSerializer) - - # Direct counters have the same key as the associated table - # Static counters have integer keys - if self.autoIncrement: - keyTypeName = "struct " + self.getTable(program).keyTypeName - else: - keyTypeName = program.config.uprefix + "32" - program.config.serializeTableDeclaration( - serializer, self.dataMapName, True, keyTypeName, - self.valueTypeName, self.getSize(program)) - - def serializeCode(self, keyname, serializer, program): - assert isinstance(serializer, ProgramSerializer) - assert isinstance(program, ebpfProgram.EbpfProgram) - - serializer.emitIndent() - serializer.appendFormat("/* Update counter {0} */", self.name) - serializer.newline() - - valueName = "ctrvalue" - initValuename = "init_val" - - serializer.emitIndent() - serializer.appendFormat("{0} *{1};", self.valueTypeName, valueName) - serializer.newline() - serializer.emitIndent() - serializer.appendFormat("{0} {1};", self.valueTypeName, initValuename) - serializer.newline() - - serializer.emitIndent() - serializer.appendLine("/* perform lookup */") - serializer.emitIndent() - program.config.serializeLookup( - serializer, self.dataMapName, keyname, valueName) - serializer.newline() - - serializer.emitIndent() - serializer.appendFormat("if ({0} != NULL) ", valueName) - serializer.newline() - serializer.increaseIndent() - serializer.emitIndent() - serializer.appendFormat("__sync_fetch_and_add({0}, {1});", - valueName, self.increment) - serializer.newline() - serializer.decreaseIndent() - serializer.emitIndent() - - serializer.append("else ") - serializer.blockStart() - serializer.emitIndent() - serializer.appendFormat("{0} = {1};", initValuename, self.increment) - serializer.newline() - - serializer.emitIndent() - program.config.serializeUpdate( - serializer, self.dataMapName, keyname, initValuename) - serializer.newline() - serializer.blockEnd(True) diff --git a/src/cc/frontends/p4/compiler/ebpfDeparser.py b/src/cc/frontends/p4/compiler/ebpfDeparser.py deleted file mode 100644 index bf3de390..00000000 --- a/src/cc/frontends/p4/compiler/ebpfDeparser.py +++ /dev/null @@ -1,173 +0,0 @@ -# Copyright (c) Barefoot Networks, Inc. -# Licensed under the Apache License, Version 2.0 (the "License") - -from collections import defaultdict, OrderedDict -from compilationException import CompilationException -from p4_hlir.hlir import parse_call, p4_field, p4_parse_value_set, \ - P4_DEFAULT, p4_parse_state, p4_table, \ - p4_conditional_node, p4_parser_exception, \ - p4_header_instance, P4_NEXT - -import ebpfProgram -import ebpfInstance -import ebpfType -import ebpfStructType -from topoSorting import Graph -from programSerializer import ProgramSerializer - -def produce_parser_topo_sorting(hlir): - # This function is copied from the P4 behavioral model implementation - header_graph = Graph() - - def walk_rec(hlir, parse_state, prev_hdr_node, tag_stacks_index): - assert(isinstance(parse_state, p4_parse_state)) - for call in parse_state.call_sequence: - call_type = call[0] - if call_type == parse_call.extract: - hdr = call[1] - - if hdr.virtual: - base_name = hdr.base_name - current_index = tag_stacks_index[base_name] - if current_index > hdr.max_index: - return - tag_stacks_index[base_name] += 1 - name = base_name + "[%d]" % current_index - hdr = hlir.p4_header_instances[name] - - if hdr not in header_graph: - header_graph.add_node(hdr) - hdr_node = header_graph.get_node(hdr) - - if prev_hdr_node: - prev_hdr_node.add_edge_to(hdr_node) - else: - header_graph.root = hdr - prev_hdr_node = hdr_node - - for branch_case, next_state in parse_state.branch_to.items(): - if not next_state: - continue - if not isinstance(next_state, p4_parse_state): - continue - walk_rec(hlir, next_state, prev_hdr_node, tag_stacks_index.copy()) - - start_state = hlir.p4_parse_states["start"] - walk_rec(hlir, start_state, None, defaultdict(int)) - - header_topo_sorting = header_graph.produce_topo_sorting() - - return header_topo_sorting - -class EbpfDeparser(object): - def __init__(self, hlir): - header_topo_sorting = produce_parser_topo_sorting(hlir) - self.headerOrder = [hdr.name for hdr in header_topo_sorting] - - def serialize(self, serializer, program): - assert isinstance(serializer, ProgramSerializer) - assert isinstance(program, ebpfProgram.EbpfProgram) - - serializer.emitIndent() - serializer.blockStart() - serializer.emitIndent() - serializer.appendLine("/* Deparser */") - serializer.emitIndent() - serializer.appendFormat("{0} = 0;", program.offsetVariableName) - serializer.newline() - for h in self.headerOrder: - header = program.getHeaderInstance(h) - self.serializeHeaderEmit(header, serializer, program) - serializer.blockEnd(True) - - def serializeHeaderEmit(self, header, serializer, program): - assert isinstance(header, ebpfInstance.EbpfHeader) - assert isinstance(serializer, ProgramSerializer) - assert isinstance(program, ebpfProgram.EbpfProgram) - p4header = header.hlirInstance - assert isinstance(p4header, p4_header_instance) - - serializer.emitIndent() - serializer.appendFormat("if ({0}.{1}.valid) ", - program.headerStructName, header.name) - serializer.blockStart() - - if ebpfProgram.EbpfProgram.isArrayElementInstance(p4header): - ebpfStack = program.getStackInstance(p4header.base_name) - assert isinstance(ebpfStack, ebpfInstance.EbpfHeaderStack) - - if isinstance(p4header.index, int): - index = "[" + str(p4header.index) + "]" - elif p4header.index is P4_NEXT: - index = "[" + ebpfStack.indexVar + "]" - else: - raise CompilationException( - True, "Unexpected index for array {0}", - p4header.index) - basetype = ebpfStack.basetype - else: - ebpfHeader = program.getHeaderInstance(p4header.name) - basetype = ebpfHeader.type - index = "" - - alignment = 0 - for field in basetype.fields: - assert isinstance(field, ebpfStructType.EbpfField) - - self.serializeFieldEmit(serializer, p4header.base_name, - index, field, alignment, program) - alignment += field.widthInBits() - alignment = alignment % 8 - serializer.blockEnd(True) - - def serializeFieldEmit(self, serializer, name, index, - field, alignment, program): - assert isinstance(index, str) - assert isinstance(name, str) - assert isinstance(field, ebpfStructType.EbpfField) - assert isinstance(serializer, ProgramSerializer) - assert isinstance(alignment, int) - assert isinstance(program, ebpfProgram.EbpfProgram) - - if field.name == "valid": - return - - fieldToEmit = (program.headerStructName + "." + name + - index + "." + field.name) - width = field.widthInBits() - if width <= 32: - store = self.generatePacketStore(fieldToEmit, 0, alignment, - width, program) - serializer.emitIndent() - serializer.appendLine(store) - else: - # Destination is bigger than 4 bytes and - # represented as a byte array. - b = (width + 7) / 8 - for i in range(0, b): - serializer.emitIndent() - store = self.generatePacketStore(fieldToEmit + "["+str(i)+"]", - i, - alignment, - 8, program) - serializer.appendLine(store) - - serializer.emitIndent() - serializer.appendFormat("{0} += {1};", - program.offsetVariableName, width) - serializer.newline() - - def generatePacketStore(self, value, offset, alignment, width, program): - assert width > 0 - assert alignment < 8 - assert isinstance(width, int) - assert isinstance(alignment, int) - - return "bpf_dins_pkt({0}, {1} / 8 + {2}, {3}, {4}, {5});".format( - program.packetName, - program.offsetVariableName, - offset, - alignment, - width, - value - ) diff --git a/src/cc/frontends/p4/compiler/ebpfInstance.py b/src/cc/frontends/p4/compiler/ebpfInstance.py deleted file mode 100644 index 822688fc..00000000 --- a/src/cc/frontends/p4/compiler/ebpfInstance.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright (c) Barefoot Networks, Inc. -# Licensed under the Apache License, Version 2.0 (the "License") - -from p4_hlir.hlir import p4_header_instance -from ebpfType import EbpfType -from compilationException import CompilationException -from programSerializer import ProgramSerializer -import typeFactory - - -class EbpfInstanceBase(object): - def __init__(self): - pass - - -class SimpleInstance(EbpfInstanceBase): - # A header or a metadata instance (but not array elements) - def __init__(self, hlirInstance, factory, isMetadata): - super(SimpleInstance, self).__init__() - self.hlirInstance = hlirInstance - self.name = hlirInstance.base_name - self.type = factory.build(hlirInstance.header_type, isMetadata) - - def declare(self, serializer): - assert isinstance(serializer, ProgramSerializer) - self.type.declare(serializer, self.name, False) - - -class EbpfHeader(SimpleInstance): - """ Represents a header instance from a P4 program """ - def __init__(self, hlirHeaderInstance, factory): - super(EbpfHeader, self).__init__(hlirHeaderInstance, factory, False) - if hlirHeaderInstance.metadata: - raise CompilationException(True, "Metadata passed to EpbfHeader") - if hlirHeaderInstance.index is not None: - self.name += "_" + str(hlirHeaderInstance.index) - - -class EbpfMetadata(SimpleInstance): - """Represents a metadata instance from a P4 program""" - def __init__(self, hlirMetadataInstance, factory): - super(EbpfMetadata, self).__init__(hlirMetadataInstance, factory, True) - if not hlirMetadataInstance.metadata: - raise CompilationException( - True, "Header instance passed to EpbfMetadata {0}", - hlirMetadataInstance) - if hlirMetadataInstance.index is not None: - raise CompilationException( - True, "Unexpected metadata array {0}", self.hlirInstance) - if hasattr(hlirMetadataInstance, "initializer"): - self.initializer = hlirMetadataInstance.initializer - else: - self.initializer = None - - def emitInitializer(self, serializer): - assert isinstance(serializer, ProgramSerializer) - if self.initializer is None: - self.type.emitInitializer(serializer) - else: - for key in self.initializer.keys(): - serializer.appendFormat( - ".{0} = {1},", key, self.initializer[key]) - - -class EbpfHeaderStack(EbpfInstanceBase): - """Represents a header stack instance; there is one instance of - this class for each STACK, and not for each - element of the stack, as in the HLIR""" - def __init__(self, hlirInstance, indexVar, factory): - super(EbpfHeaderStack, self).__init__() - - # indexVar: name of the ebpf variable that - # holds the current index for this stack - assert isinstance(indexVar, str) - assert isinstance(factory, typeFactory.EbpfTypeFactory) - assert isinstance(hlirInstance, p4_header_instance) - - self.indexVar = indexVar - self.name = hlirInstance.base_name - self.basetype = factory.build(hlirInstance.header_type, False) - assert isinstance(self.basetype, EbpfType) - self.arraySize = hlirInstance.max_index + 1 - self.hlirInstance = hlirInstance - - def declare(self, serializer): - assert isinstance(serializer, ProgramSerializer) - self.basetype.declareArray(serializer, self.name, self.arraySize) diff --git a/src/cc/frontends/p4/compiler/ebpfParser.py b/src/cc/frontends/p4/compiler/ebpfParser.py deleted file mode 100644 index 300bc691..00000000 --- a/src/cc/frontends/p4/compiler/ebpfParser.py +++ /dev/null @@ -1,427 +0,0 @@ -# Copyright (c) Barefoot Networks, Inc. -# Licensed under the Apache License, Version 2.0 (the "License") - -from p4_hlir.hlir import parse_call, p4_field, p4_parse_value_set, \ - P4_DEFAULT, p4_parse_state, p4_table, \ - p4_conditional_node, p4_parser_exception, \ - p4_header_instance, P4_NEXT -import ebpfProgram -import ebpfStructType -import ebpfInstance -import programSerializer -from compilationException import * - - -class EbpfParser(object): - def __init__(self, hlirParser): # hlirParser is a P4 parser - self.parser = hlirParser - self.name = hlirParser.name - - def serialize(self, serializer, program): - assert isinstance(serializer, programSerializer.ProgramSerializer) - assert isinstance(program, ebpfProgram.EbpfProgram) - - serializer.emitIndent() - serializer.appendFormat("{0}: ", self.name) - serializer.blockStart() - for op in self.parser.call_sequence: - self.serializeOperation(serializer, op, program) - - self.serializeBranch(serializer, self.parser.branch_on, - self.parser.branch_to, program) - - serializer.blockEnd(True) - - def serializeSelect(self, selectVarName, serializer, branch_on, program): - # selectVarName - name of temp variable to use for the select expression - assert isinstance(selectVarName, str) - assert isinstance(serializer, programSerializer.ProgramSerializer) - assert isinstance(program, ebpfProgram.EbpfProgram) - - totalWidth = 0 - switchValue = "" - for e in branch_on: - if isinstance(e, p4_field): - instance = e.instance - assert isinstance(instance, p4_header_instance) - index = "" - - if ebpfProgram.EbpfProgram.isArrayElementInstance(instance): - ebpfStack = program.getStackInstance(instance.base_name) - assert isinstance(ebpfStack, ebpfInstance.EbpfHeaderStack) - - if isinstance(instance.index, int): - index = "[" + str(instance.index) + "]" - elif instance.index is P4_NEXT: - index = "[" + ebpfStack.indexVar + "]" - else: - raise CompilationException(True, - "Unexpected index for array {0}", instance.index) - basetype = ebpfStack.basetype - name = ebpfStack.name - else: - ebpfHeader = program.getInstance(instance.name) - assert isinstance(ebpfHeader, ebpfInstance.EbpfHeader) - basetype = ebpfHeader.type - name = ebpfHeader.name - - ebpfField = basetype.getField(e.name) - assert isinstance(ebpfField, ebpfStructType.EbpfField) - - totalWidth += ebpfField.widthInBits() - fieldReference = (program.headerStructName + "." + name + - index + "." + ebpfField.name) - - if switchValue == "": - switchValue = fieldReference - else: - switchValue = ("(" + switchValue + " << " + - str(ebpfField.widthInBits()) + ")") - switchValue = switchValue + " | " + fieldReference - elif isinstance(e, tuple): - switchValue = self.currentReferenceAsString(e, program) - else: - raise CompilationException( - True, "Unexpected element in match {0}", e) - - if totalWidth > 32: - raise NotSupportedException("{0}: Matching on {1}-bit value", - branch_on, totalWidth) - serializer.emitIndent() - serializer.appendFormat("{0}32 {1} = {2};", - program.config.uprefix, - selectVarName, switchValue) - serializer.newline() - - def generatePacketLoad(self, startBit, width, alignment, program): - # Generates an expression that does a load_*, shift and mask - # to load 'width' bits starting at startBit from the current - # packet offset. - # alignment is an integer <= 8 that holds the current alignment - # of of the packet offset. - assert width > 0 - assert alignment < 8 - assert isinstance(startBit, int) - assert isinstance(width, int) - assert isinstance(alignment, int) - - firstBitIndex = startBit + alignment - lastBitIndex = startBit + width + alignment - 1 - firstWordIndex = firstBitIndex / 8 - lastWordIndex = lastBitIndex / 8 - - wordsToRead = lastWordIndex - firstWordIndex + 1 - if wordsToRead == 1: - load = "load_byte" - loadSize = 8 - elif wordsToRead == 2: - load = "load_half" - loadSize = 16 - elif wordsToRead <= 4: - load = "load_word" - loadSize = 32 - elif wordsToRead <= 8: - load = "load_dword" - loadSize = 64 - else: - raise CompilationException(True, "Attempt to load more than 1 word") - - readtype = program.config.uprefix + str(loadSize) - loadInstruction = "{0}({1}, ({2} + {3}) / 8)".format( - load, program.packetName, program.offsetVariableName, startBit) - shift = loadSize - alignment - width - load = "(({0}) >> ({1}))".format(loadInstruction, shift) - if width != loadSize: - mask = " & EBPF_MASK({0}, {1})".format(readtype, width) - else: - mask = "" - return load + mask - - def currentReferenceAsString(self, tpl, program): - # a string describing an expression of the form current(position, width) - # The assumption is that at this point the packet cursor is ALWAYS - # byte aligned. This should be true because headers are supposed - # to have sizes an integral number of bytes. - assert isinstance(tpl, tuple) - if len(tpl) != 2: - raise CompilationException( - True, "{0} Expected a tuple with 2 elements", tpl) - - minIndex = tpl[0] - totalWidth = tpl[1] - result = self.generatePacketLoad( - minIndex, totalWidth, 0, program) # alignment is 0 - return result - - def serializeCases(self, selectVarName, serializer, branch_to, program): - assert isinstance(selectVarName, str) - assert isinstance(serializer, programSerializer.ProgramSerializer) - assert isinstance(program, ebpfProgram.EbpfProgram) - - branches = 0 - seenDefault = False - for e in branch_to.keys(): - serializer.emitIndent() - value = branch_to[e] - - if isinstance(e, int): - serializer.appendFormat("if ({0} == {1})", selectVarName, e) - elif isinstance(e, tuple): - serializer.appendFormat( - "if (({0} & {1}) == {2})", selectVarName, e[0], e[1]) - elif isinstance(e, p4_parse_value_set): - raise NotSupportedException("{0}: Parser value sets", e) - elif e is P4_DEFAULT: - seenDefault = True - if branches > 0: - serializer.append("else") - else: - raise CompilationException( - True, "Unexpected element in match case {0}", e) - - branches += 1 - serializer.newline() - serializer.increaseIndent() - serializer.emitIndent() - - label = program.getLabel(value) - - if isinstance(value, p4_parse_state): - serializer.appendFormat("goto {0};", label) - elif isinstance(value, p4_table): - serializer.appendFormat("goto {0};", label) - elif isinstance(value, p4_conditional_node): - serializer.appendFormat("goto {0};", label) - elif isinstance(value, p4_parser_exception): - raise CompilationException(True, "Not yet implemented") - else: - raise CompilationException( - True, "Unexpected element in match case {0}", value) - - serializer.decreaseIndent() - serializer.newline() - - # Must create default if it is missing - if not seenDefault: - serializer.emitIndent() - serializer.appendFormat( - "{0} = p4_pe_unhandled_select;", program.errorName) - serializer.newline() - serializer.emitIndent() - serializer.appendFormat("default: goto end;") - serializer.newline() - - def serializeBranch(self, serializer, branch_on, branch_to, program): - assert isinstance(serializer, programSerializer.ProgramSerializer) - assert isinstance(program, ebpfProgram.EbpfProgram) - - if branch_on == []: - dest = branch_to.values()[0] - serializer.emitIndent() - name = program.getLabel(dest) - serializer.appendFormat("goto {0};", name) - serializer.newline() - elif isinstance(branch_on, list): - tmpvar = program.generateNewName("tmp") - self.serializeSelect(tmpvar, serializer, branch_on, program) - self.serializeCases(tmpvar, serializer, branch_to, program) - else: - raise CompilationException( - True, "Unexpected branch_on {0}", branch_on) - - def serializeOperation(self, serializer, op, program): - assert isinstance(serializer, programSerializer.ProgramSerializer) - assert isinstance(program, ebpfProgram.EbpfProgram) - - operation = op[0] - if operation is parse_call.extract: - self.serializeExtract(serializer, op[1], program) - elif operation is parse_call.set: - self.serializeMetadataSet(serializer, op[1], op[2], program) - else: - raise CompilationException( - True, "Unexpected operation in parser {0}", op) - - def serializeFieldExtract(self, serializer, headerInstanceName, - index, field, alignment, program): - assert isinstance(index, str) - assert isinstance(headerInstanceName, str) - assert isinstance(field, ebpfStructType.EbpfField) - assert isinstance(serializer, programSerializer.ProgramSerializer) - assert isinstance(alignment, int) - assert isinstance(program, ebpfProgram.EbpfProgram) - - fieldToExtractTo = headerInstanceName + index + "." + field.name - - serializer.emitIndent() - width = field.widthInBits() - if field.name == "valid": - serializer.appendFormat( - "{0}.{1} = 1;", program.headerStructName, fieldToExtractTo) - serializer.newline() - return - - serializer.appendFormat("if ({0}->len < BYTES({1} + {2})) ", - program.packetName, - program.offsetVariableName, width) - serializer.blockStart() - serializer.emitIndent() - serializer.appendFormat("{0} = p4_pe_header_too_short;", - program.errorName) - serializer.newline() - serializer.emitIndent() - serializer.appendLine("goto end;") - # TODO: jump to correct exception handler - serializer.blockEnd(True) - - if width <= 32: - serializer.emitIndent() - load = self.generatePacketLoad(0, width, alignment, program) - - serializer.appendFormat("{0}.{1} = {2};", - program.headerStructName, - fieldToExtractTo, load) - serializer.newline() - else: - # Destination is bigger than 4 bytes and - # represented as a byte array. - if alignment == 0: - shift = 0 - else: - shift = 8 - alignment - - assert shift >= 0 - if shift == 0: - method = "load_byte" - else: - method = "load_half" - b = (width + 7) / 8 - for i in range(0, b): - serializer.emitIndent() - serializer.appendFormat("{0}.{1}[{2}] = ({3}8)", - program.headerStructName, - fieldToExtractTo, i, - program.config.uprefix) - serializer.appendFormat("(({0}({1}, ({2} / 8) + {3}) >> {4})", - method, program.packetName, - program.offsetVariableName, i, shift) - if (i == b - 1) and (width % 8 != 0): - serializer.appendFormat(" & EBPF_MASK({0}8, {1})", - program.config.uprefix, width % 8) - serializer.append(")") - serializer.endOfStatement(True) - - serializer.emitIndent() - serializer.appendFormat("{0} += {1};", - program.offsetVariableName, width) - serializer.newline() - - def serializeExtract(self, serializer, headerInstance, program): - assert isinstance(serializer, programSerializer.ProgramSerializer) - assert isinstance(headerInstance, p4_header_instance) - assert isinstance(program, ebpfProgram.EbpfProgram) - - if ebpfProgram.EbpfProgram.isArrayElementInstance(headerInstance): - ebpfStack = program.getStackInstance(headerInstance.base_name) - assert isinstance(ebpfStack, ebpfInstance.EbpfHeaderStack) - - # write bounds check - serializer.emitIndent() - serializer.appendFormat("if ({0} >= {1}) ", - ebpfStack.indexVar, ebpfStack.arraySize) - serializer.blockStart() - serializer.emitIndent() - serializer.appendFormat("{0} = p4_pe_index_out_of_bounds;", - program.errorName) - serializer.newline() - serializer.emitIndent() - serializer.appendLine("goto end;") - serializer.blockEnd(True) - - if isinstance(headerInstance.index, int): - index = "[" + str(headerInstance.index) + "]" - elif headerInstance.index is P4_NEXT: - index = "[" + ebpfStack.indexVar + "]" - else: - raise CompilationException( - True, "Unexpected index for array {0}", - headerInstance.index) - basetype = ebpfStack.basetype - else: - ebpfHeader = program.getHeaderInstance(headerInstance.name) - basetype = ebpfHeader.type - index = "" - - # extract all fields - alignment = 0 - for field in basetype.fields: - assert isinstance(field, ebpfStructType.EbpfField) - - self.serializeFieldExtract(serializer, headerInstance.base_name, - index, field, alignment, program) - alignment += field.widthInBits() - alignment = alignment % 8 - - if ebpfProgram.EbpfProgram.isArrayElementInstance(headerInstance): - # increment stack index - ebpfStack = program.getStackInstance(headerInstance.base_name) - assert isinstance(ebpfStack, ebpfInstance.EbpfHeaderStack) - - # write bounds check - serializer.emitIndent() - serializer.appendFormat("{0}++;", ebpfStack.indexVar) - serializer.newline() - - def serializeMetadataSet(self, serializer, field, value, program): - assert isinstance(serializer, programSerializer.ProgramSerializer) - assert isinstance(program, ebpfProgram.EbpfProgram) - assert isinstance(field, p4_field) - - dest = program.getInstance(field.instance.name) - assert isinstance(dest, ebpfInstance.SimpleInstance) - destType = dest.type - assert isinstance(destType, ebpfStructType.EbpfStructType) - destField = destType.getField(field.name) - - if destField.widthInBits() > 32: - useMemcpy = True - bytesToCopy = destField.widthInBits() / 8 - if destField.widthInBits() % 8 != 0: - raise CompilationException( - True, - "{0}: Not implemented: wide field w. sz not multiple of 8", - field) - else: - useMemcpy = False - bytesToCopy = None # not needed, but compiler is confused - - serializer.emitIndent() - destination = "{0}.{1}.{2}".format( - program.metadataStructName, dest.name, destField.name) - if isinstance(value, int): - source = str(value) - if useMemcpy: - raise CompilationException( - True, - "{0}: Not implemented: copying from wide constant", - value) - elif isinstance(value, tuple): - source = self.currentReferenceAsString(value, program) - elif isinstance(value, p4_field): - source = program.getInstance(value.instance.name) - if isinstance(source, ebpfInstance.EbpfMetadata): - sourceStruct = program.metadataStructName - else: - sourceStruct = program.headerStructName - source = "{0}.{1}.{2}".format(sourceStruct, source.name, value.name) - else: - raise CompilationException( - True, "Unexpected type for parse_call.set {0}", value) - - if useMemcpy: - serializer.appendFormat("memcpy(&{0}, &{1}, {2})", - destination, source, bytesToCopy) - else: - serializer.appendFormat("{0} = {1}", destination, source) - - serializer.endOfStatement(True) diff --git a/src/cc/frontends/p4/compiler/ebpfProgram.py b/src/cc/frontends/p4/compiler/ebpfProgram.py deleted file mode 100644 index 12371751..00000000 --- a/src/cc/frontends/p4/compiler/ebpfProgram.py +++ /dev/null @@ -1,506 +0,0 @@ -# Copyright (c) Barefoot Networks, Inc. -# Licensed under the Apache License, Version 2.0 (the "License") - -from p4_hlir.hlir import p4_header_instance, p4_table, \ - p4_conditional_node, p4_action, p4_parse_state -from p4_hlir.main import HLIR -import typeFactory -import ebpfTable -import ebpfParser -import ebpfAction -import ebpfInstance -import ebpfConditional -import ebpfCounter -import ebpfDeparser -import programSerializer -import target -from compilationException import * - - -class EbpfProgram(object): - def __init__(self, name, hlir, isRouter, config): - """Representation of an EbpfProgram (in fact, - a C program that is converted to EBPF)""" - assert isinstance(hlir, HLIR) - assert isinstance(isRouter, bool) - assert isinstance(config, target.TargetConfig) - - self.hlir = hlir - self.name = name - self.uniqueNameCounter = 0 - self.config = config - self.isRouter = isRouter - self.reservedPrefix = "ebpf_" - - assert isinstance(config, target.TargetConfig) - - self.packetName = self.reservedPrefix + "packet" - self.dropBit = self.reservedPrefix + "drop" - self.license = "GPL" - self.offsetVariableName = self.reservedPrefix + "packetOffsetInBits" - self.zeroKeyName = self.reservedPrefix + "zero" - self.arrayIndexType = self.config.uprefix + "32" - # all array tables must be indexed with u32 values - - self.errorName = self.reservedPrefix + "error" - self.functionName = self.reservedPrefix + "filter" - self.egressPortName = "egress_port" # Hardwired in P4 definition - - self.typeFactory = typeFactory.EbpfTypeFactory(config) - self.errorCodes = [ - "p4_pe_no_error", - "p4_pe_index_out_of_bounds", - "p4_pe_out_of_packet", - "p4_pe_header_too_long", - "p4_pe_header_too_short", - "p4_pe_unhandled_select", - "p4_pe_checksum"] - - self.actions = [] - self.conditionals = [] - self.tables = [] - self.headers = [] # header instances - self.metadata = [] # metadata instances - self.stacks = [] # header stack instances EbpfHeaderStack - self.parsers = [] # all parsers - self.deparser = None - self.entryPoints = [] # control-flow entry points from parser - self.counters = [] - self.entryPointLabels = {} # maps p4_node from entryPoints - # to labels in the C program - self.egressEntry = None - - self.construct() - - self.headersStructTypeName = self.reservedPrefix + "headers_t" - self.headerStructName = self.reservedPrefix + "headers" - self.metadataStructTypeName = self.reservedPrefix + "metadata_t" - self.metadataStructName = self.reservedPrefix + "metadata" - - def construct(self): - if len(self.hlir.p4_field_list_calculations) > 0: - raise NotSupportedException( - "{0} calculated field", - self.hlir.p4_field_list_calculations.values()[0].name) - - for h in self.hlir.p4_header_instances.values(): - if h.max_index is not None: - assert isinstance(h, p4_header_instance) - if h.index == 0: - # header stack; allocate only for zero-th index - indexVarName = self.generateNewName(h.base_name + "_index") - stack = ebpfInstance.EbpfHeaderStack( - h, indexVarName, self.typeFactory) - self.stacks.append(stack) - elif h.metadata: - metadata = ebpfInstance.EbpfMetadata(h, self.typeFactory) - self.metadata.append(metadata) - else: - header = ebpfInstance.EbpfHeader(h, self.typeFactory) - self.headers.append(header) - - for p in self.hlir.p4_parse_states.values(): - parser = ebpfParser.EbpfParser(p) - self.parsers.append(parser) - - for a in self.hlir.p4_actions.values(): - if self.isInternalAction(a): - continue - action = ebpfAction.EbpfAction(a, self) - self.actions.append(action) - - for c in self.hlir.p4_counters.values(): - counter = ebpfCounter.EbpfCounter(c, self) - self.counters.append(counter) - - for t in self.hlir.p4_tables.values(): - table = ebpfTable.EbpfTable(t, self, self.config) - self.tables.append(table) - - for n in self.hlir.p4_ingress_ptr.keys(): - self.entryPoints.append(n) - - for n in self.hlir.p4_conditional_nodes.values(): - conditional = ebpfConditional.EbpfConditional(n, self) - self.conditionals.append(conditional) - - self.egressEntry = self.hlir.p4_egress_ptr - self.deparser = ebpfDeparser.EbpfDeparser(self.hlir) - - def isInternalAction(self, action): - # This is a heuristic really to guess which actions are built-in - # Unfortunately there seems to be no other way to do this - return action.lineno < 0 - - @staticmethod - def isArrayElementInstance(headerInstance): - assert isinstance(headerInstance, p4_header_instance) - return headerInstance.max_index is not None - - def emitWarning(self, formatString, *message): - assert isinstance(formatString, str) - print("WARNING: ", formatString.format(*message)) - - def toC(self, serializer): - assert isinstance(serializer, programSerializer.ProgramSerializer) - - self.generateIncludes(serializer) - self.generatePreamble(serializer) - self.generateTypes(serializer) - self.generateTables(serializer) - - serializer.newline() - serializer.emitIndent() - self.config.serializeCodeSection(serializer) - serializer.newline() - serializer.emitIndent() - serializer.appendFormat("int {0}(struct __sk_buff* {1}) ", - self.functionName, self.packetName) - serializer.blockStart() - - self.generateHeaderInstance(serializer) - serializer.append(" = ") - self.generateInitializeHeaders(serializer) - serializer.endOfStatement(True) - - self.generateMetadataInstance(serializer) - serializer.append(" = ") - self.generateInitializeMetadata(serializer) - serializer.endOfStatement(True) - - self.createLocalVariables(serializer) - serializer.newline() - - serializer.emitIndent() - serializer.appendLine("goto start;") - - self.generateParser(serializer) - self.generatePipeline(serializer) - - self.generateDeparser(serializer) - - serializer.emitIndent() - serializer.appendLine("end:") - serializer.emitIndent() - - if isinstance(self.config, target.KernelSamplesConfig): - serializer.appendFormat("return {0};", self.dropBit) - serializer.newline() - elif isinstance(self.config, target.BccConfig): - if self.isRouter: - serializer.appendFormat("if (!{0})", self.dropBit) - serializer.newline() - serializer.increaseIndent() - serializer.emitIndent() - serializer.appendFormat( - "bpf_clone_redirect({0}, {1}.standard_metadata.{2}, 0);", - self.packetName, self.metadataStructName, - self.egressPortName) - serializer.newline() - serializer.decreaseIndent() - - serializer.emitIndent() - serializer.appendLine( - "return TC_ACT_SHOT /* drop packet; clone is forwarded */;") - else: - serializer.appendFormat( - "return {1} ? TC_ACT_SHOT : TC_ACT_PIPE;", - self.dropBit) - serializer.newline() - else: - raise CompilationException( - True, "Unexpected target configuration {0}", - self.config.targetName) - serializer.blockEnd(True) - - self.generateLicense(serializer) - - serializer.append(self.config.postamble) - - def generateLicense(self, serializer): - self.config.serializeLicense(serializer, self.license) - - # noinspection PyMethodMayBeStatic - def generateIncludes(self, serializer): - assert isinstance(serializer, programSerializer.ProgramSerializer) - serializer.append(self.config.getIncludes()) - - def getLabel(self, p4node): - # C label that corresponds to this point in the control-flow - if p4node is None: - return "end" - elif isinstance(p4node, p4_parse_state): - label = p4node.name - self.entryPointLabels[p4node.name] = label - if p4node.name not in self.entryPointLabels: - label = self.generateNewName(p4node.name) - self.entryPointLabels[p4node.name] = label - return self.entryPointLabels[p4node.name] - - # noinspection PyMethodMayBeStatic - def generatePreamble(self, serializer): - assert isinstance(serializer, programSerializer.ProgramSerializer) - - serializer.emitIndent() - serializer.append("enum ErrorCode ") - serializer.blockStart() - for error in self.errorCodes: - serializer.emitIndent() - serializer.appendFormat("{0},", error) - serializer.newline() - serializer.blockEnd(False) - serializer.endOfStatement(True) - serializer.newline() - - serializer.appendLine( - "#define EBPF_MASK(t, w) ((((t)(1)) << (w)) - (t)1)") - serializer.appendLine("#define BYTES(w) ((w + 7) / 8)") - - self.config.generateDword(serializer) - - # noinspection PyMethodMayBeStatic - def generateNewName(self, base): # base is a string - """Generates a fresh name based on the specified base name""" - # TODO: this should be made "safer" - assert isinstance(base, str) - - base += "_" + str(self.uniqueNameCounter) - self.uniqueNameCounter += 1 - return base - - def generateTypes(self, serializer): - assert isinstance(serializer, programSerializer.ProgramSerializer) - - for t in self.typeFactory.type_map.values(): - t.serialize(serializer) - - # generate a new struct type for the packet itself - serializer.appendFormat("struct {0} ", self.headersStructTypeName) - serializer.blockStart() - for h in self.headers: - serializer.emitIndent() - h.declare(serializer) - serializer.endOfStatement(True) - - for h in self.stacks: - assert isinstance(h, ebpfInstance.EbpfHeaderStack) - - serializer.emitIndent() - h.declare(serializer) - serializer.endOfStatement(True) - - serializer.blockEnd(False) - serializer.endOfStatement(True) - - # generate a new struct type for the metadata - serializer.appendFormat("struct {0} ", self.metadataStructTypeName) - serializer.blockStart() - for h in self.metadata: - assert isinstance(h, ebpfInstance.EbpfMetadata) - - serializer.emitIndent() - h.declare(serializer) - serializer.endOfStatement(True) - serializer.blockEnd(False) - serializer.endOfStatement(True) - - def generateTables(self, serializer): - assert isinstance(serializer, programSerializer.ProgramSerializer) - - for t in self.tables: - t.serialize(serializer, self) - - for c in self.counters: - c.serialize(serializer, self) - - def generateHeaderInstance(self, serializer): - assert isinstance(serializer, programSerializer.ProgramSerializer) - - serializer.emitIndent() - serializer.appendFormat( - "struct {0} {1}", self.headersStructTypeName, self.headerStructName) - - def generateInitializeHeaders(self, serializer): - assert isinstance(serializer, programSerializer.ProgramSerializer) - - serializer.blockStart() - for h in self.headers: - serializer.emitIndent() - serializer.appendFormat(".{0} = ", h.name) - h.type.emitInitializer(serializer) - serializer.appendLine(",") - serializer.blockEnd(False) - - def generateMetadataInstance(self, serializer): - assert isinstance(serializer, programSerializer.ProgramSerializer) - - serializer.emitIndent() - serializer.appendFormat( - "struct {0} {1}", - self.metadataStructTypeName, - self.metadataStructName) - - def generateDeparser(self, serializer): - self.deparser.serialize(serializer, self) - - def generateInitializeMetadata(self, serializer): - assert isinstance(serializer, programSerializer.ProgramSerializer) - - serializer.blockStart() - for h in self.metadata: - serializer.emitIndent() - serializer.appendFormat(".{0} = ", h.name) - h.emitInitializer(serializer) - serializer.appendLine(",") - serializer.blockEnd(False) - - def createLocalVariables(self, serializer): - assert isinstance(serializer, programSerializer.ProgramSerializer) - - serializer.emitIndent() - serializer.appendFormat("unsigned {0} = 0;", self.offsetVariableName) - serializer.newline() - - serializer.emitIndent() - serializer.appendFormat( - "enum ErrorCode {0} = p4_pe_no_error;", self.errorName) - serializer.newline() - - serializer.emitIndent() - serializer.appendFormat( - "{0}8 {1} = 0;", self.config.uprefix, self.dropBit) - serializer.newline() - - serializer.emitIndent() - serializer.appendFormat( - "{0} {1} = 0;", self.arrayIndexType, self.zeroKeyName) - serializer.newline() - - for h in self.stacks: - serializer.emitIndent() - serializer.appendFormat( - "{0}8 {0} = 0;", self.config.uprefix, h.indexVar) - serializer.newline() - - def getStackInstance(self, name): - assert isinstance(name, str) - - for h in self.stacks: - if h.name == name: - assert isinstance(h, ebpfInstance.EbpfHeaderStack) - return h - raise CompilationException( - True, "Could not locate header stack named {0}", name) - - def getHeaderInstance(self, name): - assert isinstance(name, str) - - for h in self.headers: - if h.name == name: - assert isinstance(h, ebpfInstance.EbpfHeader) - return h - raise CompilationException( - True, "Could not locate header instance named {0}", name) - - def getInstance(self, name): - assert isinstance(name, str) - - for h in self.headers: - if h.name == name: - return h - for h in self.metadata: - if h.name == name: - return h - raise CompilationException( - True, "Could not locate instance named {0}", name) - - def getAction(self, p4action): - assert isinstance(p4action, p4_action) - for a in self.actions: - if a.name == p4action.name: - return a - - newAction = ebpfAction.BuiltinAction(p4action) - self.actions.append(newAction) - return newAction - - def getTable(self, name): - assert isinstance(name, str) - for t in self.tables: - if t.name == name: - return t - raise CompilationException( - True, "Could not locate table named {0}", name) - - def getCounter(self, name): - assert isinstance(name, str) - for t in self.counters: - if t.name == name: - return t - raise CompilationException( - True, "Could not locate counters named {0}", name) - - def getConditional(self, name): - assert isinstance(name, str) - for c in self.conditionals: - if c.name == name: - return c - raise CompilationException( - True, "Could not locate conditional named {0}", name) - - def generateParser(self, serializer): - assert isinstance(serializer, programSerializer.ProgramSerializer) - for p in self.parsers: - p.serialize(serializer, self) - - def generateIngressPipeline(self, serializer): - assert isinstance(serializer, programSerializer.ProgramSerializer) - for t in self.tables: - assert isinstance(t, ebpfTable.EbpfTable) - serializer.emitIndent() - serializer.appendFormat("{0}:", t.name) - serializer.newline() - - def generateControlFlowNode(self, serializer, node, nextEntryPoint): - # nextEntryPoint is used as a target whenever the target is None - # nextEntryPoint may also be None - if isinstance(node, p4_table): - table = self.getTable(node.name) - assert isinstance(table, ebpfTable.EbpfTable) - table.serializeCode(serializer, self, nextEntryPoint) - elif isinstance(node, p4_conditional_node): - conditional = self.getConditional(node.name) - assert isinstance(conditional, ebpfConditional.EbpfConditional) - conditional.generateCode(serializer, self, nextEntryPoint) - else: - raise CompilationException( - True, "{0} Unexpected control flow node ", node) - - def generatePipelineInternal(self, serializer, nodestoadd, nextEntryPoint): - assert isinstance(serializer, programSerializer.ProgramSerializer) - assert isinstance(nodestoadd, set) - - done = set() - while len(nodestoadd) > 0: - todo = nodestoadd.pop() - if todo in done: - continue - if todo is None: - continue - - print("Generating ", todo.name) - - done.add(todo) - self.generateControlFlowNode(serializer, todo, nextEntryPoint) - - for n in todo.next_.values(): - nodestoadd.add(n) - - def generatePipeline(self, serializer): - todo = set() - for e in self.entryPoints: - todo.add(e) - self.generatePipelineInternal(serializer, todo, self.egressEntry) - todo = set() - todo.add(self.egressEntry) - self.generatePipelineInternal(serializer, todo, None) diff --git a/src/cc/frontends/p4/compiler/ebpfScalarType.py b/src/cc/frontends/p4/compiler/ebpfScalarType.py deleted file mode 100644 index cb5db213..00000000 --- a/src/cc/frontends/p4/compiler/ebpfScalarType.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright (c) Barefoot Networks, Inc. -# Licensed under the Apache License, Version 2.0 (the "License") - -from p4_hlir.hlir import P4_AUTO_WIDTH -from ebpfType import * -from compilationException import * -from programSerializer import ProgramSerializer - - -class EbpfScalarType(EbpfType): - __doc__ = "Represents a scalar type" - def __init__(self, parent, widthInBits, isSigned, config): - super(EbpfScalarType, self).__init__(None) - assert isinstance(widthInBits, int) - assert isinstance(isSigned, bool) - self.width = widthInBits - self.isSigned = isSigned - self.config = config - if widthInBits is P4_AUTO_WIDTH: - raise NotSupportedException("{0} Variable-width field", parent) - - def widthInBits(self): - return self.width - - @staticmethod - def bytesRequired(width): - return (width + 7) / 8 - - def asString(self): - if self.isSigned: - prefix = self.config.iprefix - else: - prefix = self.config.uprefix - - if self.width <= 8: - name = prefix + "8" - elif self.width <= 16: - name = prefix + "16" - elif self.width <= 32: - name = prefix + "32" - else: - name = "char*" - return name - - def alignment(self): - if self.width <= 8: - return 1 - elif self.width <= 16: - return 2 - elif self.width <= 32: - return 4 - else: - return 1 # Char array - - def serialize(self, serializer): - assert isinstance(serializer, ProgramSerializer) - serializer.append(self.asString()) - - def declareArray(self, serializer, identifier, size): - raise CompilationException( - True, "Arrays of base type not expected in P4") - - def declare(self, serializer, identifier, asPointer): - assert isinstance(serializer, ProgramSerializer) - assert isinstance(asPointer, bool) - assert isinstance(identifier, str) - - if self.width <= 32: - self.serialize(serializer) - if asPointer: - serializer.append("*") - serializer.space() - serializer.append(identifier) - else: - if asPointer: - serializer.append("char*") - else: - serializer.appendFormat( - "char {0}[{1}]", identifier, - EbpfScalarType.bytesRequired(self.width)) - - def emitInitializer(self, serializer): - assert isinstance(serializer, ProgramSerializer) - serializer.append("0") diff --git a/src/cc/frontends/p4/compiler/ebpfStructType.py b/src/cc/frontends/p4/compiler/ebpfStructType.py deleted file mode 100644 index e279bc61..00000000 --- a/src/cc/frontends/p4/compiler/ebpfStructType.py +++ /dev/null @@ -1,128 +0,0 @@ -# Copyright (c) Barefoot Networks, Inc. -# Licensed under the Apache License, Version 2.0 (the "License") - -from p4_hlir.hlir import P4_SIGNED, P4_SATURATING -from ebpfScalarType import * - - -class EbpfField(object): - __doc__ = "represents a field in a struct type, not in an instance" - - def __init__(self, hlirParentType, name, widthInBits, attributes, config): - self.name = name - self.width = widthInBits - self.hlirType = hlirParentType - signed = False - if P4_SIGNED in attributes: - signed = True - if P4_SATURATING in attributes: - raise NotSupportedException( - "{0}.{1}: Saturated types", self.hlirType, self.name) - - try: - self.type = EbpfScalarType( - self.hlirType, widthInBits, signed, config) - except CompilationException as e: - raise CompilationException( - e.isBug, "{0}.{1}: {2}", hlirParentType, self.name, e.show()) - - def widthInBits(self): - return self.width - - -class EbpfStructType(EbpfType): - # Abstract base class for HeaderType and MetadataType. - # They are both represented by a p4 header_type - def __init__(self, hlirHeader, config): - super(EbpfStructType, self).__init__(hlirHeader) - self.name = hlirHeader.name - self.fields = [] - - for (fieldName, fieldSize) in self.hlirType.layout.items(): - attributes = self.hlirType.attributes[fieldName] - field = EbpfField( - hlirHeader, fieldName, fieldSize, attributes, config) - self.fields.append(field) - - def serialize(self, serializer): - assert isinstance(serializer, ProgramSerializer) - - serializer.emitIndent() - serializer.appendFormat("struct {0} ", self.name) - serializer.blockStart() - - for field in self.fields: - serializer.emitIndent() - field.type.declare(serializer, field.name, False) - serializer.appendFormat("; /* {0} bits */", field.widthInBits()) - serializer.newline() - - serializer.blockEnd(False) - serializer.endOfStatement(True) - - def declare(self, serializer, identifier, asPointer): - assert isinstance(serializer, ProgramSerializer) - assert isinstance(identifier, str) - assert isinstance(asPointer, bool) - - serializer.appendFormat("struct {0} ", self.name) - if asPointer: - serializer.append("*") - serializer.append(identifier) - - def widthInBits(self): - return self.hlirType.length * 8 - - def getField(self, name): - assert isinstance(name, str) - - for f in self.fields: - assert isinstance(f, EbpfField) - if f.name == name: - return f - raise CompilationException( - True, "Could not locate field {0}.{1}", self, name) - - -class EbpfHeaderType(EbpfStructType): - def __init__(self, hlirHeader, config): - super(EbpfHeaderType, self).__init__(hlirHeader, config) - validField = EbpfField(hlirHeader, "valid", 1, set(), config) - # check that no "valid" field exists already - for f in self.fields: - if f.name == "valid": - raise CompilationException( - True, - "Header type contains a field named `valid': {0}", - f) - self.fields.append(validField) - - def emitInitializer(self, serializer): - assert isinstance(serializer, ProgramSerializer) - serializer.blockStart() - serializer.emitIndent() - serializer.appendLine(".valid = 0") - serializer.blockEnd(False) - - def declareArray(self, serializer, identifier, size): - assert isinstance(serializer, ProgramSerializer) - serializer.appendFormat( - "struct {0} {1}[{2}]", self.name, identifier, size) - - -class EbpfMetadataType(EbpfStructType): - def __init__(self, hlirHeader, config): - super(EbpfMetadataType, self).__init__(hlirHeader, config) - - def emitInitializer(self, serializer): - assert isinstance(serializer, ProgramSerializer) - - serializer.blockStart() - for field in self.fields: - serializer.emitIndent() - serializer.appendFormat(".{0} = ", field.name) - - field.type.emitInitializer(serializer) - serializer.append(",") - serializer.newline() - serializer.blockEnd(False) diff --git a/src/cc/frontends/p4/compiler/ebpfTable.py b/src/cc/frontends/p4/compiler/ebpfTable.py deleted file mode 100644 index 5325028b..00000000 --- a/src/cc/frontends/p4/compiler/ebpfTable.py +++ /dev/null @@ -1,404 +0,0 @@ -# Copyright (c) Barefoot Networks, Inc. -# Licensed under the Apache License, Version 2.0 (the "License") - -from p4_hlir.hlir import p4_match_type, p4_field, p4_table, p4_header_instance -from programSerializer import ProgramSerializer -from compilationException import * -import ebpfProgram -import ebpfInstance -import ebpfCounter -import ebpfStructType -import ebpfAction - - -class EbpfTableKeyField(object): - def __init__(self, fieldname, instance, field, mask): - assert isinstance(instance, ebpfInstance.EbpfInstanceBase) - assert isinstance(field, ebpfStructType.EbpfField) - - self.keyFieldName = fieldname - self.instance = instance - self.field = field - self.mask = mask - - def serializeType(self, serializer): - assert isinstance(serializer, ProgramSerializer) - ftype = self.field.type - serializer.emitIndent() - ftype.declare(serializer, self.keyFieldName, False) - serializer.endOfStatement(True) - - def serializeConstruction(self, keyName, serializer, program): - assert isinstance(serializer, ProgramSerializer) - assert isinstance(keyName, str) - assert isinstance(program, ebpfProgram.EbpfProgram) - - if self.mask is not None: - maskExpression = " & {0}".format(self.mask) - else: - maskExpression = "" - - if isinstance(self.instance, ebpfInstance.EbpfMetadata): - base = program.metadataStructName - else: - base = program.headerStructName - - if isinstance(self.instance, ebpfInstance.SimpleInstance): - source = "{0}.{1}.{2}".format( - base, self.instance.name, self.field.name) - else: - assert isinstance(self.instance, ebpfInstance.EbpfHeaderStack) - source = "{0}.{1}[{2}].{3}".format( - base, self.instance.name, - self.instance.hlirInstance.index, self.field.name) - destination = "{0}.{1}".format(keyName, self.keyFieldName) - size = self.field.widthInBits() - - serializer.emitIndent() - if size <= 32: - serializer.appendFormat("{0} = ({1}){2};", - destination, source, maskExpression) - else: - if maskExpression != "": - raise NotSupportedException( - "{0} Mask wider than 32 bits", self.field.hlirType) - serializer.appendFormat( - "memcpy(&{0}, &{1}, {2});", destination, source, size / 8) - - serializer.newline() - - -class EbpfTableKey(object): - def __init__(self, match_fields, program): - assert isinstance(program, ebpfProgram.EbpfProgram) - - self.expressions = [] - self.fields = [] - self.masks = [] - self.fieldNamePrefix = "key_field_" - self.program = program - - fieldNumber = 0 - for f in match_fields: - field = f[0] - matchType = f[1] - mask = f[2] - - if ((matchType is p4_match_type.P4_MATCH_TERNARY) or - (matchType is p4_match_type.P4_MATCH_LPM) or - (matchType is p4_match_type.P4_MATCH_RANGE)): - raise NotSupportedException( - False, "Match type {0}", matchType) - - if matchType is p4_match_type.P4_MATCH_VALID: - # we should be really checking the valid field; - # p4_field is a header instance - assert isinstance(field, p4_header_instance) - instance = field - fieldname = "valid" - else: - assert isinstance(field, p4_field) - instance = field.instance - fieldname = field.name - - if ebpfProgram.EbpfProgram.isArrayElementInstance(instance): - ebpfStack = program.getStackInstance(instance.base_name) - assert isinstance(ebpfStack, ebpfInstance.EbpfHeaderStack) - basetype = ebpfStack.basetype - eInstance = program.getStackInstance(instance.base_name) - else: - ebpfHeader = program.getInstance(instance.name) - assert isinstance(ebpfHeader, ebpfInstance.SimpleInstance) - basetype = ebpfHeader.type - eInstance = program.getInstance(instance.name) - - ebpfField = basetype.getField(fieldname) - assert isinstance(ebpfField, ebpfStructType.EbpfField) - - fieldName = self.fieldNamePrefix + str(fieldNumber) - fieldNumber += 1 - keyField = EbpfTableKeyField(fieldName, eInstance, ebpfField, mask) - - self.fields.append(keyField) - self.masks.append(mask) - - @staticmethod - def fieldRank(field): - assert isinstance(field, EbpfTableKeyField) - return field.field.type.alignment() - - def serializeType(self, serializer, keyTypeName): - assert isinstance(serializer, ProgramSerializer) - serializer.emitIndent() - serializer.appendFormat("struct {0} ", keyTypeName) - serializer.blockStart() - - # Sort fields in decreasing size; this will ensure that - # there is no padding. - # Padding may cause the ebpf verification to fail, - # since padding fields are not initialized - fieldOrder = sorted( - self.fields, key=EbpfTableKey.fieldRank, reverse=True) - for f in fieldOrder: - assert isinstance(f, EbpfTableKeyField) - f.serializeType(serializer) - - serializer.blockEnd(False) - serializer.endOfStatement(True) - - def serializeConstruction(self, serializer, keyName, program): - serializer.emitIndent() - serializer.appendLine("/* construct key */") - - for f in self.fields: - f.serializeConstruction(keyName, serializer, program) - - -class EbpfTable(object): - # noinspection PyUnresolvedReferences - def __init__(self, hlirtable, program, config): - assert isinstance(hlirtable, p4_table) - assert isinstance(program, ebpfProgram.EbpfProgram) - - self.name = hlirtable.name - self.hlirtable = hlirtable - self.config = config - - self.defaultActionMapName = (program.reservedPrefix + - self.name + "_miss") - self.key = EbpfTableKey(hlirtable.match_fields, program) - self.size = hlirtable.max_size - if self.size is None: - program.emitWarning( - "{0} does not specify a max_size; using 1024", hlirtable) - self.size = 1024 - self.isHash = True # TODO: try to guess arrays when possible - self.dataMapName = self.name - self.actionEnumName = program.generateNewName(self.name + "_actions") - self.keyTypeName = program.generateNewName(self.name + "_key") - self.valueTypeName = program.generateNewName(self.name + "_value") - self.actions = [] - - if hlirtable.action_profile is not None: - raise NotSupportedException("{0}: action_profile tables", - hlirtable) - if hlirtable.support_timeout: - program.emitWarning("{0}: table timeout {1}; ignoring", - hlirtable, NotSupportedException.archError) - - self.counters = [] - if (hlirtable.attached_counters is not None): - for c in hlirtable.attached_counters: - ctr = program.getCounter(c.name) - assert isinstance(ctr, ebpfCounter.EbpfCounter) - self.counters.append(ctr) - - if (len(hlirtable.attached_meters) > 0 or - len(hlirtable.attached_registers) > 0): - program.emitWarning("{0}: meters/registers {1}; ignored", - hlirtable, NotSupportedException.archError) - - for a in hlirtable.actions: - action = program.getAction(a) - self.actions.append(action) - - def serializeKeyType(self, serializer): - assert isinstance(serializer, ProgramSerializer) - self.key.serializeType(serializer, self.keyTypeName) - - def serializeActionArguments(self, serializer, action): - assert isinstance(serializer, ProgramSerializer) - assert isinstance(action, ebpfAction.EbpfActionBase) - action.serializeArgumentsAsStruct(serializer) - - def serializeValueType(self, serializer): - assert isinstance(serializer, ProgramSerializer) - # create an enum with tags for all actions - serializer.emitIndent() - serializer.appendFormat("enum {0} ", self.actionEnumName) - serializer.blockStart() - - for a in self.actions: - name = a.name - serializer.emitIndent() - serializer.appendFormat("{0}_{1},", self.name, name) - serializer.newline() - - serializer.blockEnd(False) - serializer.endOfStatement(True) - - # a type-safe union: a struct with a tag and an union - serializer.emitIndent() - serializer.appendFormat("struct {0} ", self.valueTypeName) - serializer.blockStart() - - serializer.emitIndent() - #serializer.appendFormat("enum {0} action;", self.actionEnumName) - # teporary workaround bcc bug - serializer.appendFormat("{0}32 action;", - self.config.uprefix) - serializer.newline() - - serializer.emitIndent() - serializer.append("union ") - serializer.blockStart() - - for a in self.actions: - self.serializeActionArguments(serializer, a) - - serializer.blockEnd(False) - serializer.space() - serializer.appendLine("u;") - serializer.blockEnd(False) - serializer.endOfStatement(True) - - def serialize(self, serializer, program): - assert isinstance(serializer, ProgramSerializer) - assert isinstance(program, ebpfProgram.EbpfProgram) - - self.serializeKeyType(serializer) - self.serializeValueType(serializer) - - self.config.serializeTableDeclaration( - serializer, self.dataMapName, self.isHash, - "struct " + self.keyTypeName, - "struct " + self.valueTypeName, self.size) - self.config.serializeTableDeclaration( - serializer, self.defaultActionMapName, False, - program.arrayIndexType, "struct " + self.valueTypeName, 1) - - def serializeCode(self, serializer, program, nextNode): - assert isinstance(serializer, ProgramSerializer) - assert isinstance(program, ebpfProgram.EbpfProgram) - - hitVarName = program.reservedPrefix + "hit" - keyname = "key" - valueName = "value" - - serializer.newline() - serializer.emitIndent() - serializer.appendFormat("{0}:", program.getLabel(self)) - serializer.newline() - - serializer.emitIndent() - serializer.blockStart() - - serializer.emitIndent() - serializer.appendFormat("{0}8 {1};", program.config.uprefix, hitVarName) - serializer.newline() - - serializer.emitIndent() - serializer.appendFormat("struct {0} {1} = {{}};", self.keyTypeName, keyname) - serializer.newline() - - serializer.emitIndent() - serializer.appendFormat( - "struct {0} *{1};", self.valueTypeName, valueName) - serializer.newline() - - self.key.serializeConstruction(serializer, keyname, program) - - serializer.emitIndent() - serializer.appendFormat("{0} = 1;", hitVarName) - serializer.newline() - - serializer.emitIndent() - serializer.appendLine("/* perform lookup */") - serializer.emitIndent() - program.config.serializeLookup( - serializer, self.dataMapName, keyname, valueName) - serializer.newline() - - serializer.emitIndent() - serializer.appendFormat("if ({0} == NULL) ", valueName) - serializer.blockStart() - - serializer.emitIndent() - serializer.appendFormat("{0} = 0;", hitVarName) - serializer.newline() - - serializer.emitIndent() - serializer.appendLine("/* miss; find default action */") - serializer.emitIndent() - program.config.serializeLookup( - serializer, self.defaultActionMapName, - program.zeroKeyName, valueName) - serializer.newline() - serializer.blockEnd(True) - - if len(self.counters) > 0: - serializer.emitIndent() - serializer.append("else ") - serializer.blockStart() - for c in self.counters: - assert isinstance(c, ebpfCounter.EbpfCounter) - if c.autoIncrement: - serializer.emitIndent() - serializer.blockStart() - c.serializeCode(keyname, serializer, program) - serializer.blockEnd(True) - serializer.blockEnd(True) - - serializer.emitIndent() - serializer.appendFormat("if ({0} != NULL) ", valueName) - serializer.blockStart() - serializer.emitIndent() - serializer.appendLine("/* run action */") - self.runAction(serializer, self.name, valueName, program, nextNode) - - nextNode = self.hlirtable.next_ - if "hit" in nextNode: - node = nextNode["hit"] - if node is None: - node = nextNode - label = program.getLabel(node) - serializer.emitIndent() - serializer.appendFormat("if (hit) goto {0};", label) - serializer.newline() - - node = nextNode["miss"] - if node is None: - node = nextNode - label = program.getLabel(node) - serializer.emitIndent() - serializer.appendFormat("else goto {0};", label) - serializer.newline() - - serializer.blockEnd(True) - if not "hit" in nextNode: - # Catch-all - serializer.emitIndent() - serializer.appendFormat("goto end;") - serializer.newline() - - serializer.blockEnd(True) - - def runAction(self, serializer, tableName, valueName, program, nextNode): - serializer.emitIndent() - serializer.appendFormat("switch ({0}->action) ", valueName) - serializer.blockStart() - - for a in self.actions: - assert isinstance(a, ebpfAction.EbpfActionBase) - - serializer.emitIndent() - serializer.appendFormat("case {0}_{1}: ", tableName, a.name) - serializer.newline() - serializer.emitIndent() - serializer.blockStart() - a.serializeBody(serializer, valueName, program) - serializer.blockEnd(True) - serializer.emitIndent() - - nextNodes = self.hlirtable.next_ - if a.hliraction in nextNodes: - node = nextNodes[a.hliraction] - if node is None: - node = nextNode - label = program.getLabel(node) - serializer.appendFormat("goto {0};", label) - else: - serializer.appendFormat("break;") - serializer.newline() - - serializer.blockEnd(True) diff --git a/src/cc/frontends/p4/compiler/ebpfType.py b/src/cc/frontends/p4/compiler/ebpfType.py deleted file mode 100644 index a6520978..00000000 --- a/src/cc/frontends/p4/compiler/ebpfType.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) Barefoot Networks, Inc. -# Licensed under the Apache License, Version 2.0 (the "License") - -from compilationException import CompilationException - -class EbpfType(object): - __doc__ = "Base class for representing a P4 type" - - def __init__(self, hlirType): - self.hlirType = hlirType - - # Methods to override - - def serialize(self, serializer): - # the type itself - raise CompilationException(True, "Method must be overridden") - - def declare(self, serializer, identifier, asPointer): - # declaration of an identifier with this type - # asPointer is a boolean; - # if true, the identifier is declared as a pointer - raise CompilationException(True, "Method must be overridden") - - def emitInitializer(self, serializer): - # A default initializer suitable for this type - raise CompilationException(True, "Method must be overridden") - - def declareArray(self, serializer, identifier, size): - # Declare an identifier with an array type with the specified size - raise CompilationException(True, "Method must be overridden") diff --git a/src/cc/frontends/p4/compiler/p4toEbpf.py b/src/cc/frontends/p4/compiler/p4toEbpf.py deleted file mode 100755 index 8500ca5a..00000000 --- a/src/cc/frontends/p4/compiler/p4toEbpf.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) Barefoot Networks, Inc. -# Licensed under the Apache License, Version 2.0 (the "License") - -# Compiler from P4 to EBPF -# (See http://www.slideshare.net/PLUMgrid/ebpf-and-linux-networking). -# This compiler in fact generates a C source file -# which can be compiled to EBPF using the LLVM compiler -# with the ebpf target. -# -# Main entry point. - -import argparse -import os -import traceback -import sys -import target -from p4_hlir.main import HLIR -from ebpfProgram import EbpfProgram -from compilationException import * -from programSerializer import ProgramSerializer - - -def get_parser(): - parser = argparse.ArgumentParser(description='p4toEbpf arguments') - parser.add_argument('source', metavar='source', type=str, - help='a P4 source file to compile') - parser.add_argument('-g', dest='generated', default="router", - help="kind of output produced: filter or router") - parser.add_argument('-o', dest='output_file', default="output.c", - help="generated C file name") - return parser - - -def process(input_args): - parser = get_parser() - args, unparsed_args = parser.parse_known_args(input_args) - - has_remaining_args = False - preprocessor_args = [] - for a in unparsed_args: - if a[:2] == "-D" or a[:2] == "-I" or a[:2] == "-U": - input_args.remove(a) - preprocessor_args.append(a) - else: - has_remaining_args = True - - # trigger error - if has_remaining_args: - parser.parse_args(input_args) - - if args.generated == "router": - isRouter = True - elif args.generated == "filter": - isRouter = False - else: - print("-g should be one of 'filter' or 'router'") - - print("*** Compiling ", args.source) - return compileP4(args.source, args.output_file, isRouter, preprocessor_args) - - -class CompileResult(object): - def __init__(self, kind, error): - self.kind = kind - self.error = error - - def __str__(self): - if self.kind == "OK": - return "Compilation successful" - else: - return "Compilation failed with error: " + self.error - - -def compileP4(inputFile, gen_file, isRouter, preprocessor_args): - h = HLIR(inputFile) - - for parg in preprocessor_args: - h.add_preprocessor_args(parg) - if not h.build(): - return CompileResult("HLIR", "Error while building HLIR") - - try: - basename = os.path.basename(inputFile) - basename = os.path.splitext(basename)[0] - - config = target.BccConfig() - e = EbpfProgram(basename, h, isRouter, config) - serializer = ProgramSerializer() - e.toC(serializer) - f = open(gen_file, 'w') - f.write(serializer.toString()) - return CompileResult("OK", "") - except CompilationException as e: - prefix = "" - if e.isBug: - prefix = "### Compiler bug: " - return CompileResult("bug", prefix + e.show()) - except NotSupportedException as e: - return CompileResult("not supported", e.show()) - except: - return CompileResult("exception", traceback.format_exc()) - - -# main entry point -if __name__ == "__main__": - result = process(sys.argv[1:]) - if result.kind != "OK": - print(str(result)) diff --git a/src/cc/frontends/p4/compiler/programSerializer.py b/src/cc/frontends/p4/compiler/programSerializer.py deleted file mode 100644 index 651e0194..00000000 --- a/src/cc/frontends/p4/compiler/programSerializer.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python - -# helper for building C program source text - -from compilationException import * - - -class ProgramSerializer(object): - def __init__(self): - self.program = "" - self.eol = "\n" - self.currentIndent = 0 - self.INDENT_AMOUNT = 4 # default indent amount - - def __str__(self): - return self.program - - def increaseIndent(self): - self.currentIndent += self.INDENT_AMOUNT - - def decreaseIndent(self): - self.currentIndent -= self.INDENT_AMOUNT - if self.currentIndent < 0: - raise CompilationException(True, "Negative indentation level") - - def toString(self): - return self.program - - def space(self): - self.append(" ") - - def newline(self): - self.program += self.eol - - def endOfStatement(self, addNewline): - self.append(";") - if addNewline: - self.newline() - - def append(self, string): - self.program += str(string) - - def appendFormat(self, format, *args): - string = format.format(*args) - self.append(string) - - def appendLine(self, string): - self.append(string) - self.newline() - - def emitIndent(self): - self.program += " " * self.currentIndent - - def blockStart(self): - self.append("{") - self.newline() - self.increaseIndent() - - def blockEnd(self, addNewline): - self.decreaseIndent() - self.emitIndent() - self.append("}") - if addNewline: - self.newline() diff --git a/src/cc/frontends/p4/compiler/target.py b/src/cc/frontends/p4/compiler/target.py deleted file mode 100644 index 9b5fb4dd..00000000 --- a/src/cc/frontends/p4/compiler/target.py +++ /dev/null @@ -1,171 +0,0 @@ -# Copyright (c) Barefoot Networks, Inc. -# Licensed under the Apache License, Version 2.0 (the "License") - -from programSerializer import ProgramSerializer - -# abstraction for isolating target-specific features - -# Base class for representing target-specific configuration -class TargetConfig(object): - def __init__(self, target): - self.targetName = target - - def getIncludes(self): - return "" - - def serializeLookup(self, serializer, tableName, key, value): - serializer.appendFormat("{0} = bpf_map_lookup_elem(&{1}, &{2});", - value, tableName, key) - - def serializeUpdate(self, serializer, tableName, key, value): - serializer.appendFormat( - "bpf_map_update_elem(&{0}, &{1}, &{2}, BPF_ANY);", - tableName, key, value) - - def serializeLicense(self, serializer, licenseString): - assert isinstance(serializer, ProgramSerializer) - serializer.emitIndent() - serializer.appendFormat( - "char _license[] {0}(\"license\") = \"{1}\";", - self.config.section, licenseString) - serializer.newline() - - def serializeCodeSection(self, serializer): - assert isinstance(serializer, ProgramSerializer) - serializer.appendFormat("{0}(\"{1}\")", self.section, self.entrySection) - - def serializeTableDeclaration(self, serializer, tableName, - isHash, keyType, valueType, size): - assert isinstance(serializer, ProgramSerializer) - assert isinstance(tableName, str) - assert isinstance(isHash, bool) - assert isinstance(keyType, str) - assert isinstance(valueType, str) - assert isinstance(size, int) - - serializer.emitIndent() - serializer.appendFormat("struct {0} {1}(\"maps\") {2} = ", - self.tableName, self.section, tableName) - serializer.blockStart() - - serializer.emitIndent() - serializer.append(".type = ") - if isHash: - serializer.appendLine("BPF_MAP_TYPE_HASH,") - else: - serializer.appendLine("BPF_MAP_TYPE_ARRAY,") - - serializer.emitIndent() - serializer.appendFormat(".{0} = sizeof(struct {1}), ", - self.tableKeyAttribute, keyType) - serializer.newline() - - serializer.emitIndent() - serializer.appendFormat(".{0} = sizeof(struct {1}), ", - self.tableValueAttribute, valueType) - serializer.newline() - - serializer.emitIndent() - serializer.appendFormat(".{0} = {1}, ", self.tableSizeAttribute, size) - serializer.newline() - - serializer.blockEnd(False) - serializer.endOfStatement(True) - - def generateDword(self, serializer): - serializer.appendFormat( - "static inline {0}64 load_dword(void *skb, {0}64 off)", - self.uprefix) - serializer.newline() - serializer.blockStart() - serializer.emitIndent() - serializer.appendFormat( - ("return (({0}64)load_word(skb, off) << 32) | " + - "load_word(skb, off + 4);"), - self.uprefix) - serializer.newline() - serializer.blockEnd(True) - - -# Represents a target that is compiled within the kernel -# source tree samples folder and which attaches to a socket -class KernelSamplesConfig(TargetConfig): - def __init__(self): - super(TargetConfig, self).__init__("Socket") - self.entrySection = "socket1" - self.section = "SEC" - self.uprefix = "u" - self.iprefix = "i" - self.tableKeyAttribute = "key_size" - self.tableValueAttribute = "value_size" - self.tableSizeAttribute = "max_entries" - self.tableName = "bpf_map_def" - self.postamble = "" - - def getIncludes(self): - return """ -#include -#include -#include -#include -#include -#include -#include "bpf_helpers.h" -""" - - -# Represents a target compiled by bcc that uses the TC -class BccConfig(TargetConfig): - def __init__(self): - super(BccConfig, self).__init__("BCC") - self.uprefix = "u" - self.iprefix = "i" - self.postamble = "" - - def serializeTableDeclaration(self, serializer, tableName, - isHash, keyType, valueType, size): - assert isinstance(serializer, ProgramSerializer) - assert isinstance(tableName, str) - assert isinstance(isHash, bool) - assert isinstance(keyType, str) - assert isinstance(valueType, str) - assert isinstance(size, int) - - serializer.emitIndent() - if isHash: - kind = "hash" - else: - kind = "array" - serializer.appendFormat( - "BPF_TABLE(\"{0}\", {1}, {2}, {3}, {4});", - kind, keyType, valueType, tableName, size) - serializer.newline() - - def serializeLookup(self, serializer, tableName, key, value): - serializer.appendFormat("{0} = {1}.lookup(&{2});", - value, tableName, key) - - def serializeUpdate(self, serializer, tableName, key, value): - serializer.appendFormat("{0}.update(&{1}, &{2});", - tableName, key, value) - - def generateDword(self, serializer): - pass - - def serializeCodeSection(self, serializer): - pass - - def getIncludes(self): - return """ -#include -#include -#include -#include -#include -#include -#include -""" - - def serializeLicense(self, serializer, licenseString): - assert isinstance(serializer, ProgramSerializer) - pass diff --git a/src/cc/frontends/p4/compiler/topoSorting.py b/src/cc/frontends/p4/compiler/topoSorting.py deleted file mode 100644 index 21daba35..00000000 --- a/src/cc/frontends/p4/compiler/topoSorting.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright 2013-present Barefoot Networks, Inc. -# -# 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. -# - -# -# Antonin Bas (antonin@barefootnetworks.com) -# -# - -# -*- coding: utf-8 -*- - -from __future__ import print_function - -class Node(object): - def __init__(self, n): - self.n = n - self.edges = set() - - def add_edge_to(self, other): - assert(isinstance(other, Node)) - self.edges.add(other) - - def __str__(self): - return str(self.n) - - -class Graph(object): - def __init__(self): - self.nodes = {} - self.root = None - - def add_node(self, node): - assert(node not in self.nodes) - self.nodes[node] = Node(node) - - def __contains__(self, node): - return node in self.nodes - - def get_node(self, node): - return self.nodes[node] - - def produce_topo_sorting(self): - def visit(node, topo_sorting, sequence=None): - if sequence is not None: - sequence += [str(node)] - if node._behavioral_topo_sorting_mark == 1: - if sequence is not None: - print("cycle", sequence) - return False - if node._behavioral_topo_sorting_mark != 2: - node._behavioral_topo_sorting_mark = 1 - for next_node in node.edges: - res = visit(next_node, topo_sorting, sequence) - if not res: - return False - node._behavioral_topo_sorting_mark = 2 - topo_sorting.insert(0, node.n) - return True - - has_cycle = False - topo_sorting = [] - - for node in self.nodes.values(): - # 0 is unmarked, 1 is temp, 2 is permanent - node._behavioral_topo_sorting_mark = 0 - for node in self.nodes.values(): - if node._behavioral_topo_sorting_mark == 0: - if not visit(node, topo_sorting, sequence=[]): - has_cycle = True - break - # removing mark - for node in self.nodes.values(): - del node._behavioral_topo_sorting_mark - - if has_cycle: - return None - - return topo_sorting diff --git a/src/cc/frontends/p4/compiler/typeFactory.py b/src/cc/frontends/p4/compiler/typeFactory.py deleted file mode 100644 index 71a02075..00000000 --- a/src/cc/frontends/p4/compiler/typeFactory.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (c) Barefoot Networks, Inc. -# Licensed under the Apache License, Version 2.0 (the "License") - -from p4_hlir.hlir import p4_header -from ebpfStructType import * - -class EbpfTypeFactory(object): - def __init__(self, config): - self.type_map = {} - self.config = config - - def build(self, hlirType, asMetadata): - name = hlirType.name - if hlirType.name in self.type_map: - retval = self.type_map[name] - if ((not asMetadata and isinstance(retval, EbpfMetadataType)) or - (asMetadata and isinstance(retval, EbpfHeaderType))): - raise CompilationException( - True, "Same type used both as a header and metadata {0}", - hlirType) - - if isinstance(hlirType, p4_header): - if asMetadata: - type = EbpfMetadataType(hlirType, self.config) - else: - type = EbpfHeaderType(hlirType, self.config) - else: - raise CompilationException(True, "Unexpected type {0}", hlirType) - self.registerType(name, type) - return type - - def registerType(self, name, ebpfType): - self.type_map[name] = ebpfType diff --git a/src/cc/frontends/p4/docs/README.md b/src/cc/frontends/p4/docs/README.md deleted file mode 100644 index 5f949334..00000000 --- a/src/cc/frontends/p4/docs/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# External references - -See [p4toEbpf-bcc.pdf](https://github.com/iovisor/bpf-docs/blob/master/p4/p4toEbpf-bcc.pdf) diff --git a/src/cc/frontends/p4/scope.png b/src/cc/frontends/p4/scope.png deleted file mode 100644 index 585f8cf59974bbd3f16e2988dec7932e2070bbdd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 101072 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

J&n^ZrxN#CSPRm+opHb9oX>b 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 u=0(OvII>=pfUOkCdf9&Dv*!j60{C->EBPF compiler - -- cleanup.sh should be run if for some reason endToEndTest.py crashes - and leaves garbage namespaces or links - -- testP4toEbpf.py compiles all P4 files in the testprograms folder and - deposits the corresponding C files in the testoutputs folder - -- endToEndTest.py runs a complete end-to-end test compiling the - testprograms/simple.p4 program, creating a virtual network with 3 - boxes (using network namespaces): client, server, switch, loading - the EBPF into the kernel of the switch box using the TC, and - implementing the forwarding in the switch solely using the P4 - program. - - diff --git a/src/cc/frontends/p4/test/cleanup.sh b/src/cc/frontends/p4/test/cleanup.sh deleted file mode 100755 index 0c143876..00000000 --- a/src/cc/frontends/p4/test/cleanup.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -# Run this script if for some reason the endToEndTest.py crashed -# and left some garbage state - -ip netns del sw -ip netns del srv -ip netns del clt - -ip link del dev veth-clt-sw -ip link del dev veth-srv-sw - diff --git a/src/cc/frontends/p4/test/endToEndTest.py b/src/cc/frontends/p4/test/endToEndTest.py deleted file mode 100755 index 11337195..00000000 --- a/src/cc/frontends/p4/test/endToEndTest.py +++ /dev/null @@ -1,376 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright (c) Barefoot Networks, Inc. -# Licensed under the Apache License, Version 2.0 (the "License") - -# Testing example for P4->EBPF compiler -# -# This program exercises the simple.c EBPF program -# generated from the simple.p4 source file. - -from __future__ import print_function -import subprocess -import ctypes -import time -import sys -import os -from bcc import BPF -from pyroute2 import IPRoute, NSPopen, NetNS -from netaddr import IPAddress - -### This part is a simple generic network simulaton toolkit - -class Base(object): - def __init__(self): - self.verbose = True - - def message(self, *args): - if self.verbose: - print(*args) - - -class Endpoint(Base): - # a network interface really - def __init__(self, ipaddress, ethaddress): - Base.__init__(self) - self.mac_addr = ethaddress - self.ipaddress = ipaddress - self.prefixlen = 24 - self.parent = None - - def __str__(self): - return "Endpoint " + str(self.ipaddress) - - def set_parent(self, parent): - assert isinstance(parent, Node) - self.parent = parent - - def get_ip_address(self): - return IPAddress(self.ipaddress) - - -class Node(Base): - # Used to represent one of clt, sw, srv - # Each lives in its own namespace - def __init__(self, name): - Base.__init__(self) - self.name = name - self.endpoints = [] - self.get_ns() # as a side-effect creates namespace - - def add_endpoint(self, endpoint): - assert isinstance(endpoint, Endpoint) - self.endpoints.append(endpoint) - endpoint.set_parent(self) - - def __str__(self): - return "Node " + self.name - - def get_ns_name(self): - return self.name - - def get_ns(self): - nsname = self.get_ns_name() - ns = NetNS(nsname) - return ns - - def remove(self): - ns = self.get_ns(); - ns.close() - ns.remove() - - def execute(self, command): - # Run a command in the node's namespace - # Return the command's exit code - self.message(self.name, "Executing", command) - nsn = self.get_ns_name() - pipe = NSPopen(nsn, command) - result = pipe.wait() - pipe.release() - return result - - def set_arp(self, destination): - assert isinstance(destination, Endpoint) - command = ["arp", "-s", str(destination.ipaddress), - str(destination.mac_addr)] - self.execute(command) - - -class NetworkBase(Base): - def __init__(self): - Base.__init__(self) - self.ipr = IPRoute() - self.nodes = [] - - def add_node(self, node): - assert isinstance(node, Node) - self.nodes.append(node) - - def get_interface_name(self, source, dest): - assert isinstance(source, Node) - assert isinstance(dest, Node) - interface_name = "veth-" + source.name + "-" + dest.name - return interface_name - - def get_interface(self, ifname): - interfaces = self.ipr.link_lookup(ifname=ifname) - if len(interfaces) != 1: - raise Exception("Could not identify interface " + ifname) - ix = interfaces[0] - assert isinstance(ix, int) - return ix - - def set_interface_ipaddress(self, node, ifname, address, mask): - # Ask a node to set the specified interface address - if address is None: - return - - assert isinstance(node, Node) - command = ["ip", "addr", "add", str(address) + "/" + str(mask), - "dev", str(ifname)] - result = node.execute(command) - assert(result == 0) - - def create_link(self, src, dest): - assert isinstance(src, Endpoint) - assert isinstance(dest, Endpoint) - - ifname = self.get_interface_name(src.parent, dest.parent) - destname = self.get_interface_name(dest.parent, src.parent) - self.ipr.link_create(ifname=ifname, kind="veth", peer=destname) - - self.message("Create", ifname, "link") - - # Set source endpoint information - ix = self.get_interface(ifname) - self.ipr.link("set", index=ix, address=src.mac_addr) - # push source endpoint into source namespace - self.ipr.link("set", index=ix, - net_ns_fd=src.parent.get_ns_name(), state="up") - # Set interface ip address; seems to be - # lost of set prior to moving to namespace - self.set_interface_ipaddress( - src.parent, ifname, src.ipaddress , src.prefixlen) - - # Sef destination endpoint information - ix = self.get_interface(destname) - self.ipr.link("set", index=ix, address=dest.mac_addr) - # push destination endpoint into the destination namespace - self.ipr.link("set", index=ix, - net_ns_fd=dest.parent.get_ns_name(), state="up") - # Set interface ip address - self.set_interface_ipaddress(dest.parent, destname, - dest.ipaddress, dest.prefixlen) - - def show_interfaces(self, node): - cmd = ["ip", "addr"] - if node is None: - # Run with no namespace - subprocess.call(cmd) - else: - # Run in node's namespace - assert isinstance(node, Node) - self.message("Enumerating all interfaces in ", node.name) - node.execute(cmd) - - def delete(self): - self.message("Deleting virtual network") - for n in self.nodes: - n.remove() - self.ipr.close() - - -### Here begins the concrete instantiation of the network -# Network setup: -# Each of these is a separate namespace. -# -# 62:ce:1b:48:3e:61 a2:59:94:cf:51:09 -# 96:a4:85:fe:2a:11 62:ce:1b:48:3e:60 -# /------------------\ /-----------------\ -# ---------- -------- --------- -# | clt | | sw | | srv | -# ---------- -------- --------- -# 10.0.0.11 10.0.0.10 -# - -class SimulatedNetwork(NetworkBase): - def __init__(self): - NetworkBase.__init__(self) - - self.client = Node("clt") - self.add_node(self.client) - self.client_endpoint = Endpoint("10.0.0.11", "96:a4:85:fe:2a:11") - self.client.add_endpoint(self.client_endpoint) - - self.server = Node("srv") - self.add_node(self.server) - self.server_endpoint = Endpoint("10.0.0.10", "a2:59:94:cf:51:09") - self.server.add_endpoint(self.server_endpoint) - - self.switch = Node("sw") - self.add_node(self.switch) - self.sw_clt_endpoint = Endpoint(None, "62:ce:1b:48:3e:61") - self.sw_srv_endpoint = Endpoint(None, "62:ce:1b:48:3e:60") - self.switch.add_endpoint(self.sw_clt_endpoint) - self.switch.add_endpoint(self.sw_srv_endpoint) - - def run_method_in_node(self, node, method, args): - # run a method of the SimulatedNetwork class in a different namespace - # return the exit code - assert isinstance(node, Node) - assert isinstance(args, list) - torun = __file__ - args.insert(0, torun) - args.insert(1, method) - return node.execute(args) # runs the command argv[0] method args - - def instantiate(self): - # Creates the various namespaces - self.message("Creating virtual network") - - self.message("Create client-switch link") - self.create_link(self.client_endpoint, self.sw_clt_endpoint) - - self.message("Create server-switch link") - self.create_link(self.server_endpoint, self.sw_srv_endpoint) - - self.show_interfaces(self.client) - self.show_interfaces(self.server) - self.show_interfaces(self.switch) - - self.message("Set ARP mappings") - self.client.set_arp(self.server_endpoint) - self.server.set_arp(self.client_endpoint) - - def setup_switch(self): - # This method is run in the switch namespace. - self.message("Compiling and loading BPF program") - - b = BPF(src_file="./simple.c", debug=0) - fn = b.load_func("ebpf_filter", BPF.SCHED_CLS) - - self.message("BPF program loaded") - - self.message("Discovering tables") - routing_tbl = b.get_table("routing") - routing_miss_tbl = b.get_table("ebpf_routing_miss") - cnt_tbl = b.get_table("cnt") - - self.message("Hooking up BPF classifiers using TC") - - interfname = self.get_interface_name(self.switch, self.server) - sw_srv_idx = self.get_interface(interfname) - self.ipr.tc("add", "ingress", sw_srv_idx, "ffff:") - self.ipr.tc("add-filter", "bpf", sw_srv_idx, ":1", fd=fn.fd, - name=fn.name, parent="ffff:", action="ok", classid=1) - - interfname = self.get_interface_name(self.switch, self.client) - sw_clt_idx = self.get_interface(interfname) - self.ipr.tc("add", "ingress", sw_clt_idx, "ffff:") - self.ipr.tc("add-filter", "bpf", sw_clt_idx, ":1", fd=fn.fd, - name=fn.name, parent="ffff:", action="ok", classid=1) - - self.message("Populating tables from the control plane") - cltip = self.client_endpoint.get_ip_address() - srvip = self.server_endpoint.get_ip_address() - - # BCC does not support tbl.Leaf when the type contains a union, - # so we have to make up the value type manually. Unfortunately - # these sizes are not portable... - - class Forward(ctypes.Structure): - _fields_ = [("port", ctypes.c_ushort)] - - class Nop(ctypes.Structure): - _fields_ = [] - - class Union(ctypes.Union): - _fields_ = [("nop", Nop), - ("forward", Forward)] - - class Value(ctypes.Structure): - _fields_ = [("action", ctypes.c_uint), - ("u", Union)] - - if False: - # This is how it should ideally be done, but it does not work - routing_tbl[routing_tbl.Key(int(cltip))] = routing_tbl.Leaf( - 1, sw_clt_idx) - routing_tbl[routing_tbl.Key(int(srvip))] = routing_tbl.Leaf( - 1, sw_srv_idx) - else: - v1 = Value() - v1.action = 1 - v1.u.forward.port = sw_clt_idx - - v2 = Value() - v2.action = 1; - v2.u.forward.port = sw_srv_idx - - routing_tbl[routing_tbl.Key(int(cltip))] = v1 - routing_tbl[routing_tbl.Key(int(srvip))] = v2 - - self.message("Dumping table contents") - for key, leaf in routing_tbl.items(): - self.message(str(IPAddress(key.key_field_0)), - leaf.action, leaf.u.forward.port) - - def run(self): - self.message("Pinging server from client") - ping = ["ping", self.server_endpoint.ipaddress, "-c", "2"] - result = self.client.execute(ping) - if result != 0: - raise Exception("Test failed") - else: - print("Test succeeded!") - - def prepare_switch(self): - self.message("Configuring switch") - # Re-invokes this script in the switch namespace; - # this causes the setup_switch method to be run in that context. - # This is the same as running self.setup_switch() - # but in the switch namespace - self.run_method_in_node(self.switch, "setup_switch", []) - - -def compile(source, destination): - try: - status = subprocess.call( - "../compiler/p4toEbpf.py " + source + " -o " + destination, - shell=True) - if status < 0: - print("Child was terminated by signal", -status, file=sys.stderr) - else: - print("Child returned", status, file=sys.stderr) - except OSError as e: - print("Execution failed:", e, file=sys.stderr) - raise e - -def start_simulation(): - compile("testprograms/simple.p4", "simple.c") - network = SimulatedNetwork() - network.instantiate() - network.prepare_switch() - network.run() - network.delete() - os.remove("simple.c") - -def main(argv): - print(str(argv)) - if len(argv) == 1: - # Main entry point: start simulation - start_simulation() - else: - # We are invoked with some arguments (probably in a different namespace) - # First argument is a method name, rest are method arguments. - # Create a SimulatedNetwork and invoke the specified method with the - # specified arguments. - network = SimulatedNetwork() - methodname = argv[1] - arguments = argv[2:] - method = getattr(network, methodname) - method(*arguments) - -if __name__ == '__main__': - main(sys.argv) - diff --git a/src/cc/frontends/p4/test/testP4toEbpf.py b/src/cc/frontends/p4/test/testP4toEbpf.py deleted file mode 100755 index 5406f596..00000000 --- a/src/cc/frontends/p4/test/testP4toEbpf.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) Barefoot Networks, Inc. -# Licensed under the Apache License, Version 2.0 (the "License") - -# Runs the compiler on all files in the 'testprograms' folder -# Writes outputs in the 'testoutputs' folder - -from __future__ import print_function -from bcc import BPF -import os, sys -sys.path.append("../compiler") # To get hold of p4toEbpf - # We want to run it without installing it -import p4toEbpf -import os - -def drop_extension(filename): - return os.path.splitext(os.path.basename(filename))[0] - -filesFailed = {} # map error kind -> list[ (file, error) ] - -def set_error(kind, file, error): - if kind in filesFailed: - filesFailed[kind].append((file, error)) - else: - filesFailed[kind] = [(file, error)] - -def is_root(): - # Is this code portable? - return os.getuid() == 0 - -def main(): - testpath = "testprograms" - destFolder = "testoutputs" - files = os.listdir(testpath) - files.sort() - filesDone = 0 - errors = 0 - - if not is_root(): - print("Loading EBPF programs requires root privilege.") - print("Will only test compilation, not loading.") - print("(Run with sudo to test program loading.)") - - for f in files: - path = os.path.join(testpath, f) - - if not os.path.isfile(path): - continue - if not path.endswith(".p4"): - continue - - destname = drop_extension(path) + ".c" - destname = os.path.join(destFolder, destname) - - args = [path, "-o", destname] - - result = p4toEbpf.process(args) - if result.kind != "OK": - errors += 1 - print(path, result.error) - set_error(result.kind, path, result.error) - else: - # Try to load the compiled function - if is_root(): - try: - print("Compiling and loading BPF program") - b = BPF(src_file=destname, debug=0) - fn = b.load_func("ebpf_filter", BPF.SCHED_CLS) - except Exception as e: - print(e) - set_error("BPF error", path, str(e)) - - filesDone += 1 - - print("Compiled", filesDone, "files", errors, "errors") - for key in sorted(filesFailed): - print(key, ":", len(filesFailed[key]), "programs") - for v in filesFailed[key]: - print("\t", v) - exit(len(filesFailed) != 0) - - -if __name__ == "__main__": - main() diff --git a/src/cc/frontends/p4/test/testoutputs/.empty b/src/cc/frontends/p4/test/testoutputs/.empty deleted file mode 100644 index e69de29b..00000000 diff --git a/src/cc/frontends/p4/test/testprograms/arrayKey.p4 b/src/cc/frontends/p4/test/testprograms/arrayKey.p4 deleted file mode 100644 index cc6f0281..00000000 --- a/src/cc/frontends/p4/test/testprograms/arrayKey.p4 +++ /dev/null @@ -1,34 +0,0 @@ -header_type ethernet_t { - fields { - dstAddr : 48; - srcAddr : 48; - etherType : 16; - } -} - -parser start { - return parse_ethernet; -} - -header ethernet_t ethernet; - -parser parse_ethernet { - extract(ethernet); - return ingress; -} - -action nop() -{} - -table routing { - reads { - ethernet.dstAddr: exact; - } - actions { nop; } - size : 512; -} - -control ingress -{ - apply(routing); -} \ No newline at end of file diff --git a/src/cc/frontends/p4/test/testprograms/basic_routing.p4 b/src/cc/frontends/p4/test/testprograms/basic_routing.p4 deleted file mode 100644 index 56440718..00000000 --- a/src/cc/frontends/p4/test/testprograms/basic_routing.p4 +++ /dev/null @@ -1,231 +0,0 @@ -/* -Copyright 2013-present Barefoot Networks, Inc. - -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. -*/ - -header_type ethernet_t { - fields { - dstAddr : 48; - srcAddr : 48; - etherType : 16; - } -} - -header_type ipv4_t { - fields { - version : 4; - ihl : 4; - diffserv : 8; - totalLen : 16; - identification : 16; - flags : 3; - fragOffset : 13; - ttl : 8; - protocol : 8; - hdrChecksum : 16; - srcAddr : 32; - dstAddr: 32; - } -} - -parser start { - return parse_ethernet; -} - -#define ETHERTYPE_IPV4 0x0800 - -header ethernet_t ethernet; - -parser parse_ethernet { - extract(ethernet); - return select(latest.etherType) { - ETHERTYPE_IPV4 : parse_ipv4; - default: ingress; - } -} - -header ipv4_t ipv4; - -/* Not yet supported on EBPF target - -field_list ipv4_checksum_list { - ipv4.version; - ipv4.ihl; - ipv4.diffserv; - ipv4.totalLen; - ipv4.identification; - ipv4.flags; - ipv4.fragOffset; - ipv4.ttl; - ipv4.protocol; - ipv4.srcAddr; - ipv4.dstAddr; -} - -field_list_calculation ipv4_checksum { - input { - ipv4_checksum_list; - } - algorithm : csum16; - output_width : 16; -} - -calculated_field ipv4.hdrChecksum { - verify ipv4_checksum; - update ipv4_checksum; -} -*/ - -parser parse_ipv4 { - extract(ipv4); - return ingress; -} - -#define PORT_VLAN_TABLE_SIZE 32768 -#define BD_TABLE_SIZE 65536 -#define IPV4_LPM_TABLE_SIZE 16384 -#define IPV4_HOST_TABLE_SIZE 131072 -#define NEXTHOP_TABLE_SIZE 32768 -#define REWRITE_MAC_TABLE_SIZE 32768 - -#define VRF_BIT_WIDTH 12 -#define BD_BIT_WIDTH 16 -#define IFINDEX_BIT_WIDTH 10 - -/* METADATA */ -header_type ingress_metadata_t { - fields { - vrf : VRF_BIT_WIDTH; /* VRF */ - bd : BD_BIT_WIDTH; /* ingress BD */ - nexthop_index : 16; /* final next hop index */ - } -} - -metadata ingress_metadata_t ingress_metadata; - -action on_miss() { -} - -action set_bd(bd) { - modify_field(ingress_metadata.bd, bd); -} - -table port_mapping { - reads { - standard_metadata.ingress_port : exact; - } - actions { - set_bd; - } - size : PORT_VLAN_TABLE_SIZE; -} - -action set_vrf(vrf) { - modify_field(ingress_metadata.vrf, vrf); -} - -table bd { - reads { - ingress_metadata.bd : exact; - } - actions { - set_vrf; - } - size : BD_TABLE_SIZE; -} - -action fib_hit_nexthop(nexthop_index) { - modify_field(ingress_metadata.nexthop_index, nexthop_index); - subtract_from_field(ipv4.ttl, 1); -} - -table ipv4_fib { - reads { - ingress_metadata.vrf : exact; - ipv4.dstAddr : exact; - } - actions { - on_miss; - fib_hit_nexthop; - } - size : IPV4_HOST_TABLE_SIZE; -} - -table ipv4_fib_lpm { - reads { - ingress_metadata.vrf : exact; - ipv4.dstAddr : exact; // lpm not supported - } - actions { - on_miss; - fib_hit_nexthop; - } - size : IPV4_LPM_TABLE_SIZE; -} - -action set_egress_details(egress_spec) { - modify_field(standard_metadata.egress_spec, egress_spec); -} - -table nexthop { - reads { - ingress_metadata.nexthop_index : exact; - } - actions { - on_miss; - set_egress_details; - } - size : NEXTHOP_TABLE_SIZE; -} - -control ingress { - if (valid(ipv4)) { - /* derive ingress_metadata.bd */ - apply(port_mapping); - - /* derive ingress_metadata.vrf */ - apply(bd); - - /* fib lookup, set ingress_metadata.nexthop_index */ - apply(ipv4_fib) { - on_miss { - apply(ipv4_fib_lpm); - } - } - - /* derive standard_metadata.egress_spec from ingress_metadata.nexthop_index */ - apply(nexthop); - } -} - -action rewrite_src_dst_mac(smac, dmac) { - modify_field(ethernet.srcAddr, smac); - modify_field(ethernet.dstAddr, dmac); -} - -table rewrite_mac { - reads { - ingress_metadata.nexthop_index : exact; - } - actions { - on_miss; - rewrite_src_dst_mac; - } - size : REWRITE_MAC_TABLE_SIZE; -} - -control egress { - /* set smac and dmac from ingress_metadata.nexthop_index */ - apply(rewrite_mac); -} \ No newline at end of file diff --git a/src/cc/frontends/p4/test/testprograms/bitfields.p4 b/src/cc/frontends/p4/test/testprograms/bitfields.p4 deleted file mode 100644 index 9123c49c..00000000 --- a/src/cc/frontends/p4/test/testprograms/bitfields.p4 +++ /dev/null @@ -1,66 +0,0 @@ -header_type ht -{ - fields - { - f1 : 1; - f2 : 2; - f3 : 3; - f4 : 4; - f5 : 5; - f6 : 6; - f7 : 7; - f8 : 8; - f9 : 9; - f10 : 10; - f11 : 11; - f12 : 12; - f13 : 13; - f14 : 14; - f15 : 15; - f16 : 16; - f17 : 17; - f18 : 18; - f19 : 19; - f20 : 20; - f21 : 21; - f22 : 22; - f23 : 23; - f24 : 24; - f25 : 25; - f26 : 26; - f27 : 27; - f28 : 28; - f29 : 29; - f30 : 30; - f31 : 31; - f32 : 32; - } -} - -header_type larget -{ - fields - { - f48 : 48; - f1: 1; - f49 : 48; - f2 : 1; - f64 : 64; - f3 : 1; - f128 : 128; - } -} - -header ht h; -header larget large; - -parser start -{ - extract(h); - extract(large); - return ingress; -} - -control ingress -{ -} diff --git a/src/cc/frontends/p4/test/testprograms/compositeArray.p4 b/src/cc/frontends/p4/test/testprograms/compositeArray.p4 deleted file mode 100644 index 55240429..00000000 --- a/src/cc/frontends/p4/test/testprograms/compositeArray.p4 +++ /dev/null @@ -1,46 +0,0 @@ -header_type ethernet_t { - fields { - dstAddr : 48; - } -} - -header_type ipv4_t { - fields { - srcAddr : 32; - } -} - -parser start { - return parse_ethernet; -} - -header ethernet_t ethernet; - -parser parse_ethernet { - extract(ethernet); - return parse_ipv4; -} - -action nop() -{} - -header ipv4_t ipv4; - -parser parse_ipv4 { - extract(ipv4); - return ingress; -} - -table routing { - reads { - ethernet.dstAddr: exact; - ipv4.srcAddr: exact; - } - actions { nop; } - size : 512; -} - -control ingress -{ - apply(routing); -} \ No newline at end of file diff --git a/src/cc/frontends/p4/test/testprograms/compositeKey.p4 b/src/cc/frontends/p4/test/testprograms/compositeKey.p4 deleted file mode 100644 index ed04e9f1..00000000 --- a/src/cc/frontends/p4/test/testprograms/compositeKey.p4 +++ /dev/null @@ -1,72 +0,0 @@ -header_type ethernet_t { - fields { - dstAddr : 48; - srcAddr : 48; - etherType : 16; - } -} - -header_type ipv4_t { - fields { - version : 4; - ihl : 4; - diffserv : 8; - totalLen : 16; - identification : 16; - flags : 3; - fragOffset : 13; - ttl : 8; - protocol : 8; - hdrChecksum : 16; - srcAddr : 32; - dstAddr: 32; - } -} - -parser start { - return parse_ethernet; -} - -header ethernet_t ethernet; - -parser parse_ethernet { - extract(ethernet); - return select(latest.etherType) { - 0x800 : parse_ipv4; - default: ingress; - } -} - -action nop() -{} - -action forward(port) -{ - modify_field(standard_metadata.egress_port, port); -} - -header ipv4_t ipv4; - -parser parse_ipv4 { - extract(ipv4); - return ingress; -} - -table routing { - reads { - ipv4.dstAddr: exact; - ipv4.srcAddr: exact; - } - actions { nop; forward; } - size : 512; -} - -counter cnt { - type: bytes; - direct: routing; -} - -control ingress -{ - apply(routing); -} \ No newline at end of file diff --git a/src/cc/frontends/p4/test/testprograms/do_nothing.p4 b/src/cc/frontends/p4/test/testprograms/do_nothing.p4 deleted file mode 100644 index 845f8d42..00000000 --- a/src/cc/frontends/p4/test/testprograms/do_nothing.p4 +++ /dev/null @@ -1,36 +0,0 @@ -/* Sample P4 program */ -header_type ethernet_t { - fields { - dstAddr : 48; - srcAddr : 48; - etherType : 16; - } -} - -parser start { - return parse_ethernet; -} - -header ethernet_t ethernet; - -parser parse_ethernet { - extract(ethernet); - return ingress; -} - -action action_0(){ - no_op(); -} - -table table_0 { - reads { - ethernet.etherType : exact; - } - actions { - action_0; - } -} - -control ingress { - apply(table_0); -} diff --git a/src/cc/frontends/p4/test/testprograms/simple.p4 b/src/cc/frontends/p4/test/testprograms/simple.p4 deleted file mode 100644 index 7f285614..00000000 --- a/src/cc/frontends/p4/test/testprograms/simple.p4 +++ /dev/null @@ -1,74 +0,0 @@ -// Routes a packet to an interface based on its IPv4 address -// Maintains a set of counters on the routing table - -header_type ethernet_t { - fields { - dstAddr : 48; - srcAddr : 48; - etherType : 16; - } -} - -header_type ipv4_t { - fields { - version : 4; - ihl : 4; - diffserv : 8; - totalLen : 16; - identification : 16; - flags : 3; - fragOffset : 13; - ttl : 8; - protocol : 8; - hdrChecksum : 16; - srcAddr : 32; - dstAddr: 32; - } -} - -parser start { - return parse_ethernet; -} - -header ethernet_t ethernet; - -parser parse_ethernet { - extract(ethernet); - return select(latest.etherType) { - 0x800 : parse_ipv4; - default: ingress; - } -} - -action nop() -{} - -action forward(port) -{ - modify_field(standard_metadata.egress_port, port); -} - -header ipv4_t ipv4; - -parser parse_ipv4 { - extract(ipv4); - return ingress; -} - -table routing { - reads { - ipv4.dstAddr: exact; - } - actions { nop; forward; } - size : 512; -} - -counter cnt { - type: bytes; - direct: routing; -} - -control ingress -{ - apply(routing); -} \ No newline at end of file -- 2.34.1