7. P4 data types
P416 is a statically-typed language. Programs that do not pass the type checker are considered invalid and rejected by the compiler. P4 provides a number of base types as well as type operators that construct derived types. Some values can be converted to a different type using casts. However, to make user intents clear, implicit casts are only allowed in a few circumstances and the range of casts available is intentionally restricted.
P4 supports the following built-in base types:
- The
voidtype, which has no values and can be used only in a few restricted circumstances. - The
errortype, which is used to convey errors in a target-independent, compiler-managed way. - The
stringtype, which can be used with compile-time known values of type string. - The
match_kindtype, which is used for describing the implementation of table lookups, bool, which represents Boolean valuesint, which represents arbitrary-sized integer values- Bit-strings of fixed width, denoted by
bit<> - Fixed-width signed integers represented using two’s complement
int<> - Bit-strings of dynamically-computed width with a fixed maximum width
varbit<>
\~ Begin P4Grammar [INCLUDE=grammar.mdk:baseType] \~ End P4Grammar
The void type
The void type is written void. It contains no values. It is not
included in the production rule baseType as it can only appear in few
restricted places in P4 programs.
The error type
The error type contains opaque distinct values that can be used to
signal errors. It is written as error. New elements of the error type
are defined with the syntax:
\~ Begin P4Grammar [INCLUDE=grammar.mdk:errorDeclaration] \~ End P4Grammar
All elements of the error type are inserted into the error
namespace, irrespective of the place where an error is defined. error
is similar to an enumeration (enum) type in other languages. A program
can contain multiple error declarations, which the compiler will merge
together. It is an error to declare the same identifier multiple times.
Expressions of type error are described in Section
[#sec-error-exprs].
For example, the following declaration creates two elements of the
error type (these errors are declared in the P4 core library):
\~ Begin P4Example error { ParseError, PacketTooShort } \~ End P4Example
The underlying representation of errors is target-dependent.
The match kind type
The match_kind type is very similar to the error type and is used to
declare a set of distinct names that may be used in a table’s key
property (described in Section [#sec-table-props]). All identifiers
are inserted into the top-level namespace. It is an error to declare the
same match_kind identifier multiple times.
\~ Begin P4Grammar [INCLUDE=grammar.mdk:matchKindDeclaration] \~ End P4Grammar
-
The P4 core library contains the following match_kind declaration:
Begin P4Example match_kind { exact, ternary, lpm }End P4Example
Architectures may support additional match_kinds. The declaration of
new match_kinds can only occur within model description files; P4
programmers cannot declare new match kinds.
Operations on values of type match_kind are described in Section
[#sec-match-kind-exprs].
The Boolean type
The Boolean type bool contains just two values, false and true.
Boolean values are not integers or bit-strings.
Strings
The type string represents strings. There are no operations on string
values; one cannot declare variables with a string type. Parameters
with type string can be only directionless (see Section
[#sec-calling-convention]). P4 does not support string manipulation
in the dataplane; the string type is only allowed for describing
compile-time known values (i.e., string literals, as discussed in
Section [#sec-string-literals]). Even so, the string type is useful,
for example, in giving the type signature for extern functions such as
the following:
\~ Begin P4Example extern void log(string message); \~ End P4Example
As another example, the following annotation indicates that the specified name should be used for a given table in the generated control-plane API:
\~ Begin P4Example @name(“acl”) table t1 { /* body omitted */ } \~ End P4Example
Integers (signed and unsigned)
P4 supports arbitrary-size integer values. The typing rules for the integer types are chosen according to the following principles:
- Inspired by C: Typing of integers is modeled after the well-defined parts of C, expanded to cope with arbitrary fixed-width integers. In particular, the type of the result of an expression only depends on the expression operands, and not on how the result of the expression is consumed.
- No undefined behaviors: P4 attempts to avoid many of C’s behaviors, which include the size of an integer (int), the results produced on overflow, and the results produced for some input combinations (e.g., shifts with negative amounts, overflows on signed numbers, etc.). P4 computations on integer types have no undefined behaviors.
- Least surprise: The P4 typing rules are chosen to behave as closely as possible to traditional well-behaved C programs.
- Forbid rather than surprise: Rather than provide surprising or undefined results (e.g., in C comparisons between signed and unsigned integers), we have chosen to forbid expressions with ambiguous interpretations. For example, P4 does not allow binary operations that combine signed and unsigned integers.
The priority of arithmetic operations is identical to C—e.g., multiplication binds tighter than addition.
Portability
No P4 target can support all possible types and operations. For example,
the type bit<23132312> is legal in P4, but it is highly unlikely to be
supported on any target in practice. Hence, each target can impose
restrictions on the types it can support. Such restrictions may include:
- The maximum width supported
- Alignment and padding constraints (e.g., arithmetic may only be supported on widths which are an integral number of bytes).
- Constraints on some operands (e.g., some architectures may only support multiplications by small values, or shifts with small values).
The documentation supplied with a target should clearly specify restrictions, and target-specific compilers should provide clear error messages when such restrictions are encountered. An architecture may reject a well-typed P4 program and still be conformant to the P4 spec. However, if an architecture accepts a P4 program as valid, the runtime program behavior should match this specification.
Unsigned integers (bit-strings)
An unsigned integer (which we also call a “bit-string”) has an arbitrary
width, expressed in bits. A bit-string of width W is declared as:
bit<W>. W must be an expression that evaluates to a local
compile-time known value (see Section [#sec-compile-time-known]) that
is a non-negative integer. When using an expression for the size, the
expression must be parenthesized. Bitstrings with width 0 are allowed;
they have no actual bits, and can only have the value 0. See
[#sec-uninitialized-values-and-writing-invalid-headers] for
additional details. Note that bit<W> type refers to both cases of
bit<W> and bit<(expression)> where the width is a compile-time known
value.
\~ Begin P4Example const bit\<32> x = 10; // 32-bit constant with value 10. const bit\<(x + 2)> y = 15; // 12-bit constant with value 15. // expression for width must use () \~ End P4Example
Bits within a bit-string are numbered from 0 to W-1. Bit 0 is the
least significant, and bit W-1 is the most significant.
For example, the type bit<128> denotes the type of bit-string values
with 128 bits numbered from 0 to 127, where bit 127 is the most
significant.
The type bit is a shorthand for bit<1>.
P4 architectures may impose additional constraints on bit types: for example, they may limit the maximum size, or they may only support some arithmetic operations on certain sizes (e.g., 16-, 32-, and 64- bit values).
All operations that can be performed on unsigned integers are described in Section [#sec-bit-ops].
Signed Integers
Signed integers are represented using two’s complement. An integer with
W bits is declared as: int<W>. W must be an expression that
evaluates to a local compile-time known (see Section
[#sec-compile-time-known]) value that is a non-negative integer. Note
that int<W> type refers to both cases of int<W> and
int<(expression)> where the width is a local compile-time known value.
Bits within an integer are numbered from 0 to W-1. Bit 0 is the
least significant, and bit W-1 is the sign bit.
For example, the type int<64> describes the type of integers
represented using exactly 64 bits with bits numbered from 0 to 63, where
bit 63 is the most significant (sign) bit.
P4 architectures may impose additional constraints on signed types: for example, they may limit the maximum size, or they may only support some arithmetic operations on certain sizes (e.g., 16-, 32-, and 64- bit values).
All operations that can be performed on signed integers are described in Section [#sec-int-ops].
A signed integer with width 1 can only have two legal values: 0 and -1.
Dynamically-sized bit-strings
Some network protocols use fields whose size is only known at runtime
(e.g., IPv4 options). To support restricted manipulations of such
values, P4 provides a special bit-string type whose size is set at
runtime, called a varbit.
The type varbit<W> denotes a bit-string with a width of at most W
bits, where W is a local compile-time known value (see Section
[#sec-compile-time-known]) that is a non-negative integer. For
example, the type varbit<120> denotes the type of bit-string values
that may have between 0 and 120 bits. Most operations that are
applicable to fixed-size bit-strings (unsigned numbers) cannot be
performed on dynamically sized bit-strings. Note that varbit<W> type
refers to both cases of varbit<W> and varbit<(expression)> where the
width is a compile-time known value.
P4 architectures may impose additional constraints on varbit types: for
example, they may limit the maximum size, or they may require varbit
values to always contain an integer number of bytes at runtime.
All operations that can be performed on varbits are described in Section [#sec-varbit-string].
Arbitrary-precision integers
The arbitrary-precision data type describes integers with an unlimited
precision. This type is written as int.
This type is reserved for integer literals and expressions that involve
only literals. No P4 runtime value can have an int type; at compile
time the compiler will convert all int values that have a runtime
component to fixed-width types, according to the rules described below.
All operations that can be performed on arbitrary-precision integers are described in Section [#sec-varint-ops]. The following example shows three constant definitions whose values are arbitrary-precision integers.
\~ Begin P4Example const int a = 5; const int b = 2 * a; const int c = b - a + 3; \~ End P4Example
Parameters with type int are not supported for actions. Parameters
with type int for other callable entities of a program, e.g. controls,
parsers, or functions, must be directionless, indicating that all calls
must provide a compile-time known value as an argument for such a
parameter. See Section [#sec-calling-convention] for more details on
directionless parameters.
Integer literal types
The types of integer literals are as follows:
- An integer with no type prefix has type
int. - A non-negative integer prefixed with an integer width
Wand the characterwhas typebit<W>. - An integer prefixed with an integer width
Wand the charactershas typeint<W>.
The table below shows several examples of integer literals and their types. For additional examples of literals see Section [#sec-literals].
Literal10 8w10 8s10 2s3 1w10 1s1 ——— |
InterpretationType isint, value is 10 Type is bit<8>, value is 10 Type is int<8>, value is 10 Type is int<2>, value is -1 (last 2 bits), overflow warning Type is bit<1>, value is 0 (last bit), overflow warning Type is int<1>, value is -1, overflow warning ——————– |
P4 provides a number of type constructors that can be used to derive additional types including:
enumheader- header stacks
structheader_uniontuple- type specialization
externparsercontrolpackage
The types header, header_union, enum, struct, extern,
parser, control, and package can only be used in type
declarations, where they introduce a new name for the type. The type can
subsequently be referred to using this identifier.
Other types cannot be declared, but are synthesized by the compiler
internally to represent the type of certain language constructs. These
types are described in Section [#sec-synth-types]: set types and
function types. For example, the programmer cannot declare a variable
with type “set”, but she can write an expression whose value evaluates
to a set type. These types are used during type-checking.
\~ Begin P4Grammar [INCLUDE=grammar.mdk:typeDeclaration]
[INCLUDE=grammar.mdk:derivedTypeDeclaration]
[INCLUDE=grammar.mdk:typeRef]
[INCLUDE=grammar.mdk:namedType]
[INCLUDE=grammar.mdk:prefixedType]
- [INCLUDE=grammar.mdk:typeName]
End P4Grammar
Enumeration types
- An enumeration type is defined using the following syntax:
Begin P4Grammar [INCLUDE=grammar.mdk:enumDeclaration]
[INCLUDE=grammar.mdk:identifierList]
[INCLUDE=grammar.mdk:specifiedIdentifierList]
-
[INCLUDE=grammar.mdk:specifiedIdentifier]
End P4Grammar -
For example, the declaration
Begin P4Example enum Suits { Clubs, Diamonds, Hearths, Spades }End P4Example
introduces a new enumeration type, which contains four elements—e.g.,
Suits.Clubs. An enum declaration introduces a new identifier in the
current scope for naming the created type along with its distinct
elements. The underlying representation of the Suits enum is not
specified, so their “size” in bits is not specified (it is
target-specific).
It is also possible to specify an enum with an underlying
representation. These are sometimes called serializable enums, because
headers are allowed to have fields with such enum types. This requires
the programmer provide both the fixed-width unsigned (or signed) integer
type and an associated integer value for each symbolic entry in the
enumeration. The symbol typeRef in the grammar above must be one of
the following types:
- an unsigned integer, i.e.
bit<W>for some local compile-time known valueW. - a signed integer, i.e.
int<W>for some local compile-time known valueW. - a type name declared via
typedef, where the base type of that type is either one of the types listed above, or anothertypedefname that meets these conditions. For example, the declaration
\~ Begin P4Example enum bit\<16> EtherType { VLAN = 0x8100, QINQ = 0x9100, MPLS = 0x8847, IPV4 = 0x0800, IPV6 = 0x86dd } \~ End P4Example
introduces a new enumeration type, which contains five elements—e.g.,
EtherType.IPV4. This enum declaration specifies the fixed-width
unsigned integer representation for each entry in the enum and
provides an underlying type: bit<16>. This kind of enum declaration
can be thought of as declaring a new bit<16> type, where variables or
fields of this type are expected to be unsigned 16-bit integer values,
and the mapping of symbolic to numeric values defined by the enum are
also defined as a part of this declaration. In this way, an enum with
an underlying type can be thought of as being a type derived from the
underlying type carrying equality, assignment, and casts to/from the
underlying type.
Compiler implementations are expected to raise an error if the fixed-width integer representation for an enumeration entry falls outside the representation range of the underlying type.
-
For example, the declaration
Begin P4Example enum bit\<8> FailingExample { first = 1, second = 2, third = 3, unrepresentable = 300 }End P4Example
would raise an error because 300, the value associated with
FailingExample.unrepresentable cannot be represented as a bit<8>
value.
The initializer expression must be a compile-time known value.
Annotations, represented by the non-terminal optAnnotations, are
described in Section [#sec-annotations].
Operations on enum values are described in Section
[#sec-enum-exprs].
Header types
The declaration of a header type is given by the following syntax:
\~ Begin P4Grammar [INCLUDE=grammar.mdk:headerTypeDeclaration]
[INCLUDE=grammar.mdk:structFieldList]
- [INCLUDE=grammar.mdk:structField]
End P4Grammar
where each typeRef is restricted to a bit-string type (fixed or
variable), a fixed-width signed integer type, a boolean type, or a
struct that itself contains other struct fields, nested arbitrarily, as
long as all of the “leaf” types are bit<W>, int<W>, a serializable
enum, or a bool. If a bool is used inside a P4 header, all
implementations encode the bool as a one bit long field, with the
value 1 representing true and 0 representing false. Field names
have to be distinct.
A header declaration introduces a new identifier in the current scope;
the type can be referred to using this identifier. A header is similar
to a struct in C, containing all the specified fields. However, in
addition, a header also contains a hidden Boolean “validity” field. When
the “validity” bit is true we say that the “header is valid”. When a
local variable with a header type is declared, its “validity” bit is
automatically set to false. The “validity” bit can be manipulated by
using the header methods isValid(), setValid(), and setInvalid(),
as described in Section [#sec-ops-on-hdrs].
Note, nesting of headers is not supported. One reason is that it leads
to complications in defining the behavior of arbitrary sequences of
setValid, setInvalid, and emit operations. Consider an example
where header h1 contains header h2 as a member, both currently
valid. A program executes h2.setInvalid() followed by
packet.emit(h1). Should all fields of h1 be emitted, but skipping
h2? Similarly, should h1.setInvalid() invalidate all headers
contained within h1, regardless of how deeply they are nested?
-
Header types may be empty:
Begin P4Example header Empty_h { }End P4Example
Note that an empty header still contains a validity bit.
When a struct is inside of a header, the order of the fields for the
purposes of extract and emit calls is the order of the fields as
defined in the source code. An example of a header including a struct is
included below.
\~ Begin P4Example struct ipv6_addr { bit\<32> Addr0; bit\<32> Addr1; bit\<32> Addr2; bit\<32> Addr3; }
header ipv6_t { bit\<4> version; bit\<8> trafficClass; bit\<20> flowLabel; bit\<16> payloadLen; bit\<8> nextHdr; bit\<8> hopLimit; ipv6_addr src; ipv6_addr dst; } \~ End P4Example
Headers that do not contain any varbit field are “fixed size.” Headers
containing varbit fields have “variable size.” The size (in bits) of a
fixed-size header is simply the sum of the sizes of all component fields
(without counting the validity bit). There is no padding or alignment of
the header fields. Targets may impose additional constraints on header
types—e.g., restricting headers to sizes that are an integer number of
bytes.
For example, the following declaration describes a typical Ethernet header:
\~ Begin P4Example header Ethernet_h { bit\<48> dstAddr; bit\<48> srcAddr; bit\<16> etherType; } \~ End P4Example
-
The following variable declaration uses the newly introduced type
Ethernet_h:
Begin P4Example Ethernet_h ethernetHeader;End P4Example
P4’s parser language provides an extract method that can be used to
“fill in” the fields of a header from a network packet, as described
in Section [#sec-packet-data-extraction]. The successful execution of
an extract operation also sets the validity bit of the extracted
header to true.
-
Here is an example of an IPv4 header with variable-sized options:
Begin P4Example header IPv4_h { bit\<4> version; bit\<4> ihl; bit\<8> diffserv; bit\<16> totalLen; bit\<16> identification; bit\<3> flags; bit\<13> fragOffset; bit\<8> ttl; bit\<8> protocol; bit\<16> hdrChecksum; bit\<32> srcAddr; bit\<32> dstAddr; varbit\<320> options; }End P4Example
As demonstrated by a code example in Section
[#sec-packet-extract-two], another way to support headers that
contain variable-length fields is to define two headers – one fixed
length, one containing a varbit field – and extract each part in
separate parsing steps.
Notice that the names isValid, setValid, minSizeInBits, etc. are
all valid header field names.
Header stacks
A header stack represents an array of headers or header unions. A header stack type is defined as:
\~ Begin P4Grammar [INCLUDE=grammar.mdk:headerStackType] \~ End P4Grammar
where typeName is the name of a header or header union type. For a
header stack hs[n], the term n is the maximum defined size, and must
be a local compile-time known value that is a positive integer. Nested
header stacks are not supported. At runtime a stack contains n values
with type typeName, only some of which may be valid. Expressions on
header stacks are discussed in Section [#sec-expr-hs].
-
For example, the following declarations,
Begin P4Example header Mpls_h { bit\<20> label; bit\<3> tc; bit bos; bit\<8> ttl; } Mpls_h[10] mpls;End P4Example
introduce a header stack called mpls containing ten entries, each of
type Mpls_h.
Operations on header stacks are described in Section [#sec-expr-hs].
Header unions
A header union represents an alternative containing at most one of several different headers. Header unions can be used to represent “options” in protocols like TCP and IP. They also provide hints to P4 compilers that only one alternative will be present, allowing them to conserve storage resources.
-
A header union is defined as:
Begin P4Grammar [INCLUDE=grammar.mdk:headerUnionDeclaration]End P4Grammar
This declaration introduces a new type with the specified name in the current scope. Each element of the list of fields used to declare a header union must be of header type. An empty list of fields is legal. Field names have to be distinct.
As an example, the type Ip_h below represents the union of an IPv4 and
IPv6 headers:
\~ Begin P4Example header_union IP_h { IPv4_h v4; IPv6_h v6; } \~ End P4Example
A header union is not considered a type with fixed length.
Operation on header unions are described in Section [#sec-expr-hu].
Struct types
-
P4
structtypes are defined with the following syntax:
Begin P4Grammar [INCLUDE=grammar.mdk:structTypeDeclaration]End P4Grammar
This declaration introduces a new type with the specified name in the
current scope. Field names have to be distinct. An empty struct (with no
fields) is legal. For example, the structure Parsed_headers below
contains the headers recognized by a simple parser:
\~ Begin P4Example header Tcp_h { /* fields omitted / } header Udp_h { / fields omitted */ } struct Parsed_headers { Ethernet_h ethernet; Ip_h ip; Tcp_h tcp; Udp_h udp; } \~ End P4Example
Tuple types
A tuple is similar to a struct, in that it holds multiple values. The
type of tuples with n component types T1,…,Tn is written as
\~ Begin P4Example tuple\<T1,/* more fields omitted */,Tn> \~ End P4Example
\~ Begin P4Grammar [INCLUDE=grammar.mdk:tupleType] \~ End P4Grammar
Operations that manipulate tuple types are described in Section [#sec-tuple-exprs].
The type tuple<> is a tuple type with no components.
List types
A list holds zero or more values, where every element must have the same
type. The type of a list where all elements have type T is written as
\~ Begin P4Example list
\~ Begin P4Grammar [INCLUDE=grammar.mdk:p4listType] \~ End P4Grammar
Operations that manipulate list types are described in Section [#sec-list-exprs].
Type nesting rules
The table below lists all types that may appear as members of headers,
header unions, structs, tuples, and lists. Note that int by itself
(i.e. not as part of an int<N> type expression) means an
arbitrary-precision integer, without a width specified.
|——————–|————————————————||||| | | Container kind ||||| |
|———–|————–|—————–|——|————–| | Element type |
header | header_union | struct or tuple | list | header stack |
+:——————-+:———-+:————-+:—————-+:—–+:————-+ | bit<W> |
allowed | error | allowed | allowed | error | | int<W> | allowed |
error | allowed | allowed | error | | varbit<W> | allowed | error |
allowed | allowed | error | | int | error | error | error | allowed |
error | | void | error | error | error | error | error | | string |
error | error | error | allowed | error | | error | error | error |
allowed | allowed | error | | match_kind | error | error | error |
allowed | error | | bool | allowed | error | allowed | allowed | error
| | enumeration types | allowed[1] | error | allowed | allowed | error
| | header types | error | allowed | allowed | allowed | allowed | |
header stacks | error | error | allowed | allowed | error | | header
unions | error | error | allowed | allowed | allowed | | struct types |
allowed[2] | error | allowed | allowed | error | | tuple types | error
| error | allowed | allowed | error | | list types | error | error |
error | allowed | error | |——————–|———–|———|———-|———|——-|
Rationale: int does not have precise storage requirements, unlike
bit<> or int<> types. match_kind values are not useful to store in
a variable, as they are only used to specify how to match fields in
table search keys, which are all declared at compile time. void is not
useful as part of another data structure. Headers must have precisely
defined formats as sequences of bits in order for them to be parsed or
deparsed.
Note the two-argument extract method (see Section
[#sec-packet-extract-two]) on packets only supports a single varbit
field in a header.
The table below lists all types that may appear as base types in a
typedef or type declaration.
|——————-|——————–|—————–| | Base type B | typedef B <name> | type B
<name> | +:——————+:——————-+:—————-+ | bit<W> | allowed | allowed | |
int<W> | allowed | allowed | | varbit<W> | allowed | error | | int
| allowed | error | | void | error | error | | string | allowed |
error | | error | allowed | error | | match_kind | error | error | |
bool | allowed | allowed | | enumeration types | allowed | error | |
header types | allowed | error | | header stacks | allowed | error | |
header unions | allowed | error | | struct types | allowed | error | |
tuple types | allowed | error | | list types | allowed | error | | a
typedef name | allowed | allowed[3] | | a type name | allowed |
allowed | |——————-|——————–|—————–|
Rationale: So far, no clear motivation for allowing typedef for void
and match_kind was presented. Therefore, to be on the safe side this
is disallowed.
Synthesized data types
For the purposes of type-checking the P4 compiler can synthesize some type representations which cannot be directly expressed by users. These are described in this section: set types and function types.
Set types
The type set<T> describes sets of values of some type T. Set types
can only appear in restricted contexts in P4 programs. For example, the
range expression 8w5 .. 8w8 describes a set containing the 8-bit
numbers 5, 6, 7, and 8, so its type is set<bit<8>>;. This expression
can be used as a label in a select expression (see Section
[#sec-select]), matching any value in this range. Set types cannot be
named or declared by P4 programmers, they are only synthesized by the
compiler internally and used for type-checking. Expressions with set
types are described in Section [#sec-set-exprs].
Function types
[]{tex-cmd: “”} Function types are created by the P4 compiler internally to represent the types of functions (explicit functions or extern functions) and methods during type-checking. We also call the type of a function its signature. Libraries can contain functions and extern function declarations.
- For example, consider the following declarations:
Begin P4Example extern void random(in bit\<5> logRange, out bit\<32> value);
bit\<32> add(in bit\<32> left, in bit\<32> right) { return left + right; } \~ End P4Example
These declarations describe two objects:
random, which has a function type, representing the following information:- the result type is
void - the function has two inputs
- the first formal parameter has direction
in, typebit<5>, and namelogRange - the second formal parameter has direction
out, typebit<32>, and namevalue
- the result type is
add, also has a function type, representing the following information:- the result type is
bit<32> - the function has two inputs
- both inputs have direction
inand typebit<32>
- the result type is
Extern types
[]{tex-cmd: “”} P4 supports extern object declarations and extern function declarations using the following syntax.
\~ Begin P4Grammar [INCLUDE=grammar.mdk:externDeclaration] \~ End P4Grammar
Extern functions
[]{tex-cmd: “”} An extern function declaration describes the name and type signature of the function, but not its implementation.
\~ Begin P4Grammar [INCLUDE=grammar.mdk:functionPrototype] \~ End P4Grammar
For an example of an extern function declaration, see Section
[#sec-function-type].
Extern objects
[]{tex-cmd: “”} An extern object declaration declares an object and
all methods that can be invoked to perform computations and to alter the
state of the object. Extern object declarations can also optionally
declare constructor methods; these must have the same name as the
enclosing extern type, no type parameters, and no return type. Extern
declarations may only appear as allowed by the architecture model and
may be specific to a target.
\~ Begin P4Grammar [INCLUDE=grammar.mdk:methodPrototypes]
methodPrototype : optAnnotations functionPrototype ‘;’ | optAnnotations TYPE_IDENTIFIER ‘(’ parameterList ‘)’ ‘;’ //constructor | optAnnotations ABSTRACT functionPrototype “;” ;
[INCLUDE=grammar.mdk:typeOrVoid]
[INCLUDE=grammar.mdk:optTypeParameters]
[INCLUDE=grammar.mdk:typeParameters]
- [INCLUDE=grammar.mdk:typeParameterList]
End P4Grammar
For example, the P4 core library introduces two extern objects
packet_in and packet_out used for manipulating packets (see Sections
[#sec-packet-data-extraction] and [#sec-deparse]). Here is an
example showing how the methods of these objects can be invoked on a
packet:
\~ Begin P4Example extern packet_out { void emit
Functions and methods are the only P4 constructs that support overloading: there can exist multiple methods with the same name in the same scope. When overloading is used, the compiler must be able to disambiguate at compile-time which method or function is being called, either by the number of arguments or by the names of the arguments, when calls are specifying argument names. Argument type information is not used in disambiguating calls.
- Notice that overloading of parsers, controls, or packages is not
allowed:
Begin P4Example parser p(packet_in p, out bit\<32> value) { … }
// The following will cause an error about a duplicate declaration //parser p(packet_in p, out Headers headers) { // … //} \~ End P4Example
Abstract methods
Typical extern object methods are built-in, and are implemented by the target architecture. P4 programmers can only call such methods.
However, some types of extern objects may provide methods that can be
implemented by the P4 programmers. Such methods are described with the
abstract keyword prior to the method definition. Here is an example:
\~ Begin P4Example extern Balancer { Balancer(); // get the number of active flows bit\<32> getFlowCount(); // return port index used for load-balancing // @param address: IPv4 source address of flow abstract bit\<4> on_new_flow(in bit\<32> address); } \~ End P4Example
When such an object is instantiated the user has to supply an
implementation of all the abstract methods (see
[#sec-instantiating-abstract-methods]).
Type specialization
A generic type may be specialized by specifying arguments for its type variables. In cases where the compiler can infer type arguments type specialization is not necessary. When a type is specialized all its type variables must be bound.
\~ Begin P4Grammar [INCLUDE=grammar.mdk:specializedType] \~ End P4Grammar
For example, the following extern declaration describes a generic block
of registers, where the type of the elements stored in each register is
an arbitrary T.
\~ Begin P4Example extern Register
The type T has to be specified when instantiating a set of registers,
by specializing the Register type:
\~ Begin P4Example Register\<bit\<32>>(128) registerBank; \~ End P4Example
The instantiation of registerBank is made using the Register type
specialized with the bit<32> bound to the T type argument.
struct, header, header_union and header stack types can be generic
as well. In order to use such a generic type it must be specialized with
appropriate type arguments. For example
\~ Begin P4Example // generic structure type struct S
struct G
// specialize S by replacing ‘T’ with ‘bit\<32>’ const S\<bit\<32>> s = { field = 32w0, valid = false }; // Specialize G by replacing ‘T’ with ‘bit\<32>’ const G\<bit\<32>> g = { s = { field = 0, valid = false } };
// generic header type header H
// Specialize H by replacing ‘T’ with ‘bit\<8>’ const H\<bit\<8>> h = { field = 1 }; // Header stack produced from a specialization of a generic header type H\<bit\<8>>[10] stack;
// Generic header union header_union HU
// Header union with a type obtained by specializing a generic header
union type HU
Parser and control blocks types
Parsers and control blocks types are similar to function types: they describe the signature of parsers and control blocks. Such functions have no return values. Declarations of parsers and control block types in architectures may be generic (i.e., have type parameters).
The types parser, control, and package cannot be used as types of
arguments for methods, parsers, controls, tables, or actions. They can
be used as types for the arguments passed to constructors (see Section
[#sec-parameterization]).
Parser type declarations
A parser type declaration describes the signature of a parser. A parser
should have at least one argument of type packet_in, representing the
received packet that is processed.
\~ Begin P4Grammar [INCLUDE=grammar.mdk:parserTypeDeclaration] \~ End P4Grammar
For example, the following is a type declaration of a parser type named
P that is parameterized on a type variable H. That parser receives
as input a packet_in value b and produces two values:
- A value with a user-defined type
H - A value with a predefined type
Counters
\~ Begin P4Example struct Counters { /* Fields omitted */ } parser
P
Control type declarations
-
A control type declaration describes the signature of a control block.
Begin P4Grammar [INCLUDE=grammar.mdk:controlTypeDeclaration]End P4Grammar
Control type declarations are similar to parser type declarations.
Package types
-
A package type describes the signature of a package.
Begin P4Grammar [INCLUDE=grammar.mdk:packageTypeDeclaration]End P4Grammar
All parameters of a package are evaluated at compilation time, and in
consequence they must all be directionless (they cannot be in, out,
or inout). Otherwise package types are very similar to parser type
declarations. Packages can only be instantiated; there are no runtime
behaviors associated with them.
Don’t care types
A don’t care (underscore, “_”) can be used in some circumstances as a
type. It should be only used in a position where one could write a bound
type variable. The underscore can be used to reduce code complexity—when
it is not important what the type variable binds to (during type
unification the don’t care type can unify with any other type). An
example is given Section [#sec-arch-desc-example].
Some P4 types define a “default value,” which can be used to automatically initialize values of that type. The default values are as follows:
- For
int,bit<N>andint<N>types the default value is 0. - For
boolthe default value isfalse. - For
errorthe default value iserror.NoError(defined in core.p4) - For
stringthe default value is the empty string"" - For
varbit<N>the default value is a string of zero bits (there is currently no P4 literal to represent such a value). - For
enumvalues with an underlying type the default value is 0, even if 0 is actually not one of the named values in the enum. - For
enumvalues without an underlying type the default value is the first value that appears in theenumtype declaration. - For
headertypes the default value isinvalid. - For header stacks the default value is that all elements are invalid
and the
nextIndexis 0. - For
header_unionvalues the default value is that all union elements are invalid. - For
structtypes the default value is astructwhere each field has the default value of the suitable field type – if all such default values are defined. - For a
tupletype the default value is atuplewhere each field has the default value of the suitable type – if all such default values are defined.
Note that some types do not have default values, e.g., match_kind, set
types, function types, extern types, parser types, control types,
package types.
Many P4 operations are restrained to expressions that evaluate to numeric values. Such expressions must have one of the following numeric types:
int- an arbitrary-precision integer (section [#sec-arbitrary-precision-integers])bit<W>- aW-bit unsigned integer whereW >= 0(section [#sec-unsigned-integers])int<W>- aW-bit signed integer whereW >= 1(section [#sec-signed-integers])- a serializable
enumwith an underlying type that isbit<W>orint<W>(section [#sec-enum-types]).
A typedef declaration can be used to give an alternative name to a
type.
\~ Begin P4Grammar typedefDeclaration : optAnnotations TYPEDEF typeRef name ‘;’ | optAnnotations TYPEDEF derivedTypeDeclaration name ‘;’ ; \~ End P4Grammar
\~ Begin P4Example typedef bit\<32> u32; typedef struct Point { int\<32> x; int\<32> y; } Pt; typedef Empty_h[32] HeaderStack; \~ End P4Example
The two types are treated as synonyms, and all operations that can be executed using the original type can be also executed using the newly created type.
If typedef is used with a generic type the type must be specialized
with the suitable number of type arguments:
\~ Begin P4Example struct S
// typedef S X; – illegal: S does not have type arguments typedef S\<bit\<32>> X; // – legal \~ End P4Example
Similarly to typedef, the keyword type can be used to introduce a
new type.
\~ Begin P4Grammar | optAnnotations TYPE typeRef name \~ End P4Grammar
\~ Begin P4Example type bit\<32> U32; U32 x = (U32)0; \~ End P4Example
While similar to typedef, the type keyword introduces a new type
which is not a synonym with the original type: values of the original
type and the newly introduced type cannot be mixed in expressions.
Currently the types that can be created by the type keyword are
restricted to one of: bit<>, int<>, bool, or types defined using
type from such types.
One important use of such types is in describing P4 values that need to be exchanged with the control plane through communication channels (e.g., through the control-plane API or through network packets sent to the control plane). For example, a P4 architecture may define a type for the switch ports:
\~Begin P4Example type bit\<9> PortId_t; \~End P4Example
This declaration will prevent PortId_t values from being used in
arithmetic expressions without casts. Moreover, this declaration may
enable special manipulation or such values by software that lies outside
of the datapath (e.g., a target-specific toolchain could include
software that automatically converts values of type PortId_t to a
different representation when exchanged with the control-plane
software).