17. Architecture description
The architecture description must be provided by the target manufacturer
in the form of a library P4 source file that contains at least one
declaration for a package; this package must be instantiated by the
user to construct a program for a target. For an example see the Very
Simple Switch declaration from Section [#sec-vss-arch].
The architecture description file may pre-define data types, constants,
helper package implementations, and errors. It must also declare the
types of all the programmable blocks that will appear in the final
target: parsers and control blocks. The programmable blocks may
optionally be grouped together in packages, which can be nested.
Since some of the target components may manipulate user-defined types, which are unknown at the target declaration time, these are described using type variables, which must be used parametrically in the program—i.e., type variables are checked similar to Java generics, not C++ templates.
\~ Figure { #fig-switcharch; caption: “Fragment of example switch architecture.” } [switcharch] \~ [switcharch]: figs/switcharch.png { width: 75%; page-align: here }
The following example describes a switch by using two packages, each containing a parser, a match-action pipeline, and a deparser:
\~ Begin P4Example parser Parser
Just from these declarations, even without reading a precise description of the target, the programmer can infer some useful information about the architecture of the described switch, as shown in Figure [#fig-switcharch]:
- The switch contains two separate
packagesIngressandEgress. - The
Parser,IPipe, andDeparserin theIngresspackage are chained together in order. In addition, theIngress.IPipeblock has an input of typeIngress.IH, which is an output of theIngress.Parser. - Similarly, the
Parser,EPipe, andDeparserare chained in theEgresspackage. - The
Ingress.IPipeis connected to theEgress.EPipe, because the first outputs a value of typeT, which is an input to the second. Note that the occurrences of the type variableTare instantiated with the same type inSwitch. In contrast, theIngresstypeIHand theEgresstypeIHmay be different. To force them to be the same, we could instead declareIHandOHat the switch level:package Switch<T,IH,OH>(Ingress<T, IH, OH> ingress, Egress<T, IH, OH> egress).
Hence, this architecture models a target switch that contains two separate channels between the ingress and egress pipeline:
- A channel that can pass data directly via its argument of type
T. On a software target with shared memory between ingress and egress this could be implemented by passing directly a pointer; on an architecture without shared memory presumably the compiler will need to automatically synthesize serialization code. - A channel that can pass data indirectly using a parser and deparser that serializes data into a packet and back.
To construct a program for the architecture, the P4 program must
instantiate a top-level package by passing values for all its
arguments creating a variable called main in the top-level namespace.
The types of the arguments must match the types of the parameters—after
a suitable substitution of the type variables. The type substitution can
be expressed directly, using type specialization, or can be inferred by
a compiler, using a unification algorithm like Hindley-Milner.
-
For example, given the following type declarations:
Begin P4Example parser Prs(packet_in b, out T result); control Pipe (in T data); package Switch (Prs p, Pipe map); End P4Example
-
and the following declarations:
Begin P4Example parser P(packet_in b, out bit\<32> index) { /* body omitted / } control Pipe1(in bit\<32> data) { / body omitted / } control Pipe2(in bit\<8> data) { / body omitted */ }End P4Example
-
The following is a legal declaration for the top-level target:
Begin P4Example Switch(P(), Pipe1()) main;End P4Example
-
And the following is illegal:
Begin P4Example Switch(P(), Pipe2()) main;End P4Example
The latter declaration is incorrect because the parser P requires T
to be bit<32>, while Pipe2 requires T to be bit<8>.
The user can also explicitly specify values for the type variables (otherwise the compiler has to infer values for these type variables):
\~ Begin P4Example Switch\<bit\<32>>(P(), Pipe1()) main; \~ End P4Example
\~ Figure { #fig-packetfilter; caption: “A packet filter target model. The parser computes a Boolean value, which is used to decide whether the packet is dropped.” } [packetfilter] \~ [packetfilter]: figs/packetfilter.png { width: 5cm; page-align: here }
To illustrate the versatility of the P4 architecture description language, we give an example of another architecture: one which models a packet filter that makes a drop/no drop decision based only on the computation in a P4 parser, as shown in Figure [#fig-packetfilter].
This model could be used to program packet filters running in the Linux
kernel. For example, we could replace the tcpdump language with the
much more powerful P4 language; P4 can seamlessly support new protocols,
while providing complete “type safety” during packet processing. For
such a target, the P4 compiler could generate an eBPF (Extended Berkeley
Packet Filter) program, which is injected by the tcpdump utility into
the Linux kernel, and executed by the eBPF kernel JIT compiler/runtime.
In this case the target is the Linux kernel, and the architecture model is a packet filter.
- The declaration for this architecture is as follows:
Begin P4Example parser Parser(packet_in packet, out H headers); control Filter (inout H headers, out bool accept); - package Program
(Parser p, Filter f);
End P4Example