15. Parameterization
In order to support libraries of useful P4 components, both parsers
and control blocks can be additionally parameterized through the use
of constructor parameters.
Consider again the parser declaration syntax:
parserDeclaration
: parserTypeDeclaration optConstructorParameters
"{" parserLocalElements parserStates "}"
;
optConstructorParameters
: /* empty */
| "(" parameterList ")"
;
From this grammar fragment we infer that a parser declaration may have
two sets of parameters:
- The runtime parser parameters (
parameterList) - Optional parser constructor parameters (
optConstructorParameters)
Constructor parameters must be directionless (i.e., they cannot be in,
out, or inout) and where the parser is instantiated, it must be
possible to fully evaluate the expressions supplied for these parameters
at compilation time.
Consider the following example:
parser GenericParser(packet_in b, out Packet_header p)
(bool udpSupport) { // constructor parameters
state start {
b.extract(p.ethernet);
transition select(p.ethernet.etherType) {
16w0x0800: ipv4;
}
}
state ipv4 {
b.extract(p.ipv4);
transition select(p.ipv4.protocol) {
6: tcp;
17: tryudp;
}
}
state tryudp {
transition select(udpSupport) {
false: accept;
true : udp;
}
}
state udp {
// body omitted
}
}
When instantiating the GenericParser it is necessary to supply a value
for the udpSupport parameter, as in the following example:
// topParser is a GenericParser where udpSupport = false
GenericParser(false) topParser;
Controls and parsers are often instantiated exactly once. As a light syntactic sugar, control and parser declarations with no constructor parameters may be applied directly, as if they were an instance. This has the effect of creating and applying a local instance of that type.
control Callee(/* parameters omitted */) { /* body omitted */ }
control Caller(/* parameters omitted */)(/* parameters omitted */) {
apply {
Callee.apply(/* arguments omitted */); // Callee is treated as an instance
}
}
The definition of Caller is equivalent to the following.
control Caller(/* parameters omitted */)(/* parameters omitted */) {
@name("Callee") Callee() Callee_inst; // local instance of Callee
apply {
Callee_inst.apply(/* arguments omitted */); // Callee_inst is applied
}
}
directApplication
: typeName "." APPLY "(" argumentList ")" ";"
| specializedType "." APPLY "(" argumentList ")" ";"
;
This feature is intended to streamline the common case where a type is instantiated exactly once.
The second production in the grammar allows direct calls for generic controls or parsers:
control Callee<T>(/* parameters omitted */) { /* body omitted */ }
control Caller(/* parameters omitted */)(/* parameters omitted */) {
apply {
// Callee<bit<32>> is treated as an instance
Callee<bit<32>>.apply(/* arguments omitted */);
}
}
For completeness, the behavior of directly invoking the same type more than once is defined as follows.
- Direct type invocation in different scopes will result in different local instances with different fully-qualified control names.
- In the same scope, direct type invocation will result in a different
local instance per invocation—however, instances of the same type
will share the same global name, via the
@nameannotation. If the type contains controllable entities, then invoking it directly more than once in the same scope is illegal, because it will produce multiple controllable entities with the same fully-qualified control name.
See Section Annotations controlling naming for details of @name
annotations.
No direct invocation is possible for controls or parsers that require constructor arguments. These need to be instantiated before they are invoked.