11. Constants and variable declarations
-
Constant values are defined with the syntax:
Begin P4Grammar [INCLUDE=grammar.mdk:constantDeclaration] -
[INCLUDE=grammar.mdk:initializer]
End P4Grammar
Such a declaration introduces a constant whose value has the specified type. The following are all legal constant declarations:
\~ Begin P4Example const bit\<32> COUNTER = 32w0x0; struct Version { bit\<32> major; bit\<32> minor; } const Version version = { 32w0, 32w0 }; \~ End P4Example
The initializer expression must be a local compile-time known value.
Local variables are declared with a type, a name, and an optional initializer (as well as an optional annotation):
\~ Begin P4Grammar [INCLUDE=grammar.mdk:variableDeclaration]
- [INCLUDE=grammar.mdk:optInitializer]
End P4Grammar
Variable declarations without an initializer are uninitialized (except
for headers and other header-related types, which are initialized to
invalid in the same way as described for direction out parameters in
Section [#sec-calling-convention]). The language places few
restrictions on the types of the variables: most P4 types that can be
written explicitly can be used (e.g., base types, struct, header,
header stack, tuple). However, it is impossible to declare variables
with type int, or with types that are only synthesized by the compiler
(e.g., set) In addition, variables of type parser, control,
package, or extern types must be declared using instantiations (see
Section [#sec-instantiations]).
Reading the value of a variable that has not been initialized yields an undefined result. The compiler should attempt to detect and emit a warning in such situations.
Variables declarations can appear in the following locations within a P4 program:
- In a block statement,
- In a
parserstate, - In an
actionbody, - In a
controlblock’sapplysub-block, - In the list of local declarations in a
parser, and - In the list of local declarations in a
control.
Variables have local scope, and behave like stack-allocated variables in languages such as C. The value of a variable is never preserved from one invocation of its enclosing block to the next. In particular, variables cannot be used to maintain state between different network packets.
Instantiations are similar to variable declarations, but are reserved
for the types with constructors (extern objects, control blocks,
parsers, and packages):
\~ Begin P4Grammar instantiation : typeRef ‘(’ argumentList ‘)’ name ‘;’ | annotations typeRef ‘(’ argumentList ‘)’ name ‘;’ ; \~ End P4Grammar
An instantiation is written as a constructor invocation followed by a name. Instantiations are always executed at compilation time (Section [#sec-compile-time-known]). The effect is to allocate an object with the specified name, and to bind it to the result of the constructor invocation. Note that instantiation arguments can be specified by name.
For example, a hypothetical bank of counter objects can be instantiated as follows:
\~ Begin P4Example // from target library enum CounterType { Packets, Bytes, Both } extern Counter { Counter(bit\<32> size, CounterType type); void increment(in bit\<32> index); } // user program control c(/* parameters omitted /) { Counter(32w1024, CounterType.Both) ctr; // instantiation apply { / body omitted */ } } \~ End P4Example
Instantiating objects with abstract methods
When instantiating an extern type that has abstract methods users have
to supply implementations for all such methods. This is done using
object initializers:
\~ Begin P4Grammar lvalue: … | THIS
expression: … | THIS
instantiation: … | annotations typeRef “(” argumentList “)” name “=” objInitializer “;” | typeRef “(” argumentList “)” name “=” objInitializer “;”
[INCLUDE=grammar.mdk:objInitializer]
[INCLUDE=grammar.mdk:objDeclarations]
- [INCLUDE=grammar.mdk:objDeclaration]
End P4Grammar
The abstract methods can only use the supplied arguments or refer to
values that are in the top-level scope. When calling another method of
the same instance the this keyword is used to indicate the current
object instance:
\~ Begin P4Example // Instantiate a balancer Balancer() b = { // provide an implementation for the abstract methods bit\<4> on_new_flow(in bit\<32> address) { // uses the address and the number of flows to balance the load bit\<32> count = this.getFlowCount(); // call method of the same instance return (address + count)[3:0]; } } \~ End P4Example
Abstract methods may be invoked by users explicitly, or they may be invoked by the target architecture. The architectural description has to specify when the abstract methods are invoked and what the meaning of their arguments and return values is; target architectures may impose additional constraints on abstract methods.
Restrictions on top-level instantiations
A P4 program may not instantiate controls and parsers in the top-level
package. This restriction is designed to ensure that most state resides
in the architecture itself, or is local to a parser or control. For
example, the following program is not valid:
\~ Begin P4Example // Program control c(/* parameters omitted /) { / body omitted */ } c() c1; // illegal top-level instantiation \~ End P4Example
because control c1 is instantiated at the top-level. Note that
top-level declarations of constants and instantiations of extern objects
are permitted.