07 01 base types
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 widthvarbit<>
baseType
: BOOL
| MATCH_KIND
| ERROR
| BIT
| STRING
| INT
| BIT "<" INTEGER ">"
| INT "<" INTEGER ">"
| VARBIT "<" INTEGER ">"
| BIT "<" "(" expression ")" ">"
| INT "<" "(" expression ")" ">"
| VARBIT "<" "(" expression ")" ">"
;
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:
errorDeclaration
: ERROR "{" identifierList "}"
;
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
Operations on error types.
For example, the following declaration creates two elements of the
error type (these errors are declared in the P4 core library):
error { ParseError, PacketTooShort }
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 Table properties). All identifiers
are inserted into the top-level namespace. It is an error to declare the
same match_kind identifier multiple times.
matchKindDeclaration
: MATCH_KIND "{" identifierList optTrailingComma "}"
;
The P4 core library contains the following match_kind declaration:
match_kind {
exact,
ternary,
lpm
}
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
Operations on match_kind types.
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
Calling convention: call by copy in/copy out). 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 String literals). Even so, the string type is useful,
for example, in giving the type signature for extern functions such as
the following:
extern void log(string message);
As another example, the following annotation indicates that the specified name should be used for a given table in the generated control-plane API:
@name("acl") table t1 { /* body omitted */ }
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 Compile-time known and local compile-time known values) 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
Reading uninitialized values and writing fields of 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.
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 ()
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 Operations on fixed-width bit types (unsigned integers).
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
Compile-time known and local compile-time known values) 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 Operations on fixed-width signed integers.
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
Compile-time known and local compile-time known values) 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 Operations on variable-size bit types.
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 Operations on arbitrary-precision integers. The following example shows three constant definitions whose values are arbitrary-precision integers.
const int a = 5;
const int b = 2 * a;
const int c = b - a + 3;
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 Calling convention: call by copy in/copy out 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 Literal constants.
| Literal | Interpretation |
|---|---|
10 |
Type is int, value is 10 |
8w10 |
Type is bit<8>, value is 10 |
8s10 |
Type is int<8>, value is 10 |
2s3 |
Type is int<2>, value is -1 (last 2 bits), overflow warning |
1w10 |
Type is bit<1>, value is 0 (last bit), overflow warning |
1s1 |
Type is int<1>, value is -1, overflow warning |