P4 is a language for programming the data plane of network devices. This document provides a precise definition of the P416 language, which is the 2016 revision of the P4 language (http://p4.org). The target audience for this document includes developers who want to write compilers, simulators, IDEs, and debuggers for P4 programs. This document may also be of interest to P4 programmers who are interested in understanding the syntax and semantics of the language at a deeper level. The PDF version of this document can be downloaded from this link.
1. Scope
This specification document defines the structure and interpretation of programs in the P416 language. It defines the syntax, semantic rules, and requirements for conformant implementations of the language.
It does not define:
-
Mechanisms by which P4 programs are compiled, loaded, and executed on packet-processing systems,
-
Mechanisms by which data are received by one packet-processing system and delivered to another system,
-
Mechanisms by which the control plane manages the match-action tables and other stateful objects defined by P4 programs,
-
The size or complexity of P4 programs,
-
The minimal requirements of packet-processing systems that are capable of providing a conformant implementation.
It is understood that some implementations may be unable to implement the behavior defined here in all cases, or may provide options to eliminate some safety guarantees in exchange for better performance or handling larger programs. They should document where they deviate from this specification.
2. Terms, definitions, and symbols
Throughout this document, the following terms will be used:
-
Architecture: A set of P4-programmable components and the data plane interfaces between them.
-
Control plane: A class of algorithms and the corresponding input and output data that are concerned with the provisioning and configuration of the data plane.
-
Data plane: A class of algorithms that describe transformations on packets by packet-processing systems.
-
Metadata: Intermediate data generated during execution of a P4 program.
-
Packet: A network packet is a formatted unit of data carried by a packet-switched network.
-
Packet header: Formatted data at the beginning of a packet. A given packet may contain a sequence of packet headers representing different network protocols.
-
Packet payload: Packet data that follows the packet headers.
-
Packet-processing system: A data-processing system designed for processing network packets. In general, packet-processing systems implement control plane and data plane algorithms.
-
Target: A packet-processing system capable of executing a P4 program.
All terms defined explicitly in this document should not be understood to refer implicitly to similar terms defined elsewhere. Conversely, any terms not defined explicitly in this document should be interpreted according to generally recognizable sources—e.g., IETF RFCs.
3. Overview
P4 is a language for expressing how packets are processed by the data plane of a programmable forwarding element such as a hardware or software switch, network interface card, router, or network appliance. The name P4 comes from the original paper that introduced the language, "Programming Protocol-independent Packet Processors," https://arxiv.org/pdf/1312.1719.pdf. While P4 was initially designed for programming switches, its scope has been broadened to cover a large variety of devices. In the rest of this document we use the generic term target for all such devices.
Many targets implement both a control plane and a data plane. P4 is designed to specify only the data plane functionality of the target. P4 programs also partially define the interface by which the control plane and the data-plane communicate, but P4 cannot be used to describe the control-plane functionality of the target. In the rest of this document, when we talk about P4 as "programming a target", we mean "programming the data plane of a target".
As a concrete example of a target, Figure 1 illustrates the difference between a traditional fixed-function switch and a P4-programmable switch. In a traditional switch the manufacturer defines the data-plane functionality. The control-plane controls the data plane by managing entries in tables (e.g. routing tables), configuring specialized objects (e.g. meters), and by processing control-packets (e.g. routing protocol packets) or asynchronous events, such as link state changes or learning notifications.
A P4-programmable switch differs from a traditional switch in two essential ways:
-
The data plane functionality is not fixed in advance but is defined by a P4 program. The data plane is configured at initialization time to implement the functionality described by the P4 program (shown by the long red arrow) and has no built-in knowledge of existing network protocols.
-
The control plane communicates with the data plane using the same channels as in a fixed-function device, but the set of tables and other objects in the data plane are no longer fixed, since they are defined by a P4 program. The P4 compiler generates the API that the control plane uses to communicate with the data plane.
Hence, P4 can be said to be protocol independent, but it enables programmers to express a rich set of protocols and other data plane behaviors.
The core abstractions provided by the P4 language are:
-
Header types describe the format (the set of fields and their sizes) of each header within a packet.
-
Parsers describe the permitted sequences of headers within received packets, how to identify those header sequences, and the headers and fields to extract from packets.
-
Tables associate user-defined keys with actions. P4 tables generalize traditional switch tables; they can be used to implement routing tables, flow lookup tables, access-control lists, and other user-defined table types, including complex multi-variable decisions.
-
Actions are code fragments that describe how packet header fields and metadata are manipulated. Actions can include data, which is supplied by the control-plane at runtime.
-
Match-action units perform the following sequence of operations:
-
Construct lookup keys from packet fields or computed metadata,
-
Perform table lookup using the constructed key, choosing an action (including the associated data) to execute, and
-
Finally, execute the selected action.
-
-
Control flow expresses an imperative program that describes packet-processing on a target, including the data-dependent sequence of match-action unit invocations. Deparsing (packet reassembly) can also be performed using a control flow.
-
Extern objects are architecture-specific constructs that can be manipulated by P4 programs through well-defined APIs, but whose internal behavior is hard-wired (e.g., checksum units) and hence not programmable using P4.
-
User-defined metadata: user-defined data structures associated with each packet.
-
Intrinsic metadata: metadata provided by the architecture associated with each packet—e.g., the input port where a packet has been received.
Figure 2 shows a typical tool workflow when programming a target using P4.
Target manufacturers provide the hardware or software implementation framework, an architecture definition, and a P4 compiler for that target. P4 programmers write programs for a specific architecture, which defines a set of P4-programmable components on the target as well as their external data plane interfaces.
Compiling a set of P4 programs produces two artifacts:
-
a data plane configuration that implements the forwarding logic described in the input program and
-
an API for managing the state of the data plane objects from the control plane
P4 is a domain-specific language that is designed to be implementable on a large variety of targets including programmable network interface cards, FPGAs, software switches, and hardware ASICs. As such, the language is restricted to constructs that can be efficiently implemented on all of these platforms.
Assuming a fixed cost for table lookup operations and interactions with extern objects, all P4 programs (i.e., parsers and controls) execute a constant number of operations for each byte of an input packet received and analyzed. Although parsers may contain loops, provided some header is extracted on each cycle, the packet itself provides a bound on the total execution of the parser. In other words, under these assumptions, the computational complexity of a P4 program is linear in the total size of all headers, and never depends on the size of the state accumulated while processing data (e.g., the number of flows, or the total number of packets processed). These guarantees are necessary (but not sufficient) for enabling fast packet processing across a variety of targets.
P4 conformance of a target is defined as follows: if a specific target T
supports only a subset of the P4 programming language, say P4T, programs
written in P4T executed on the target should provide the exact same behavior
as is described in this document. Note that P4 conformant targets can provide
arbitrary P4 language extensions and extern elements.
3.1. Benefits of P4
Compared to state-of-the-art packet-processing systems (e.g., based on writing microcode on top of custom hardware), P4 provides a number of significant advantages:
-
Flexibility: P4 makes many packet-forwarding policies expressible as programs, in contrast to traditional switches, which expose fixed-function forwarding engines to their users.
-
Expressiveness: P4 can express sophisticated, hardware-independent packet processing algorithms using solely general-purpose operations and table look-ups. Such programs are portable across hardware targets that implement the same architectures (assuming sufficient resources are available).
-
Resource mapping and management: P4 programs describe storage resources abstractly (e.g., IPv4 source address); compilers map such user-defined fields to available hardware resources and manage low-level details such as allocation and scheduling.
-
Software engineering: P4 programs provide important benefits such as type checking, information hiding, and software reuse.
-
Component libraries: Component libraries supplied by manufacturers can be used to wrap hardware-specific functions into portable high-level P4 constructs.
-
Decoupling hardware and software evolution: Target manufacturers may use abstract architectures to further decouple the evolution of low-level architectural details from high-level processing.
-
Debugging: Manufacturers can provide software models of an architecture to aid in the development and debugging of P4 programs.
3.2. P4 language evolution: comparison to previous versions (P4 v1.0/v1.1)
Compared to P414, the earlier version of the language, P416 makes a number of significant, backwards-incompatible changes to the syntax and semantics of the language. The evolution from the previous version (P414) to the current one (P416) is depicted in Figure 3. In particular, a large number of language features have been eliminated from the language and moved into libraries including counters, checksum units, meters, etc.
Hence, the language has been transformed from a complex language (more than 70 keywords) into a relatively small core language (less than 40 keywords, shown in Appendix B) accompanied by a library of fundamental constructs that are needed for writing most P4.
The v1.1 version of P4 introduced a language construct called extern that can
be used to describe library elements. Many constructs defined in the v1.1
language specification will thus be transformed into such library elements
(including constructs that have been eliminated from the language, such as
counters and meters). Some of these extern objects are expected to be
standardized, and they will be in the scope of a future document describing a
standard library of P4 elements. In this document we provide several examples
of extern constructs. P416 also introduces and repurposes some v1.1
language constructs for describing the programmable parts of an architecture.
These language constructs are: parser, state, control, and package.
One important goal of the P416 language revision is to provide a stable language definition. In other words, we strive to ensure that all programs written in P416 will remain syntactically correct and behave identically when treated as programs for future versions of the language. Moreover, if some future version of the language requires breaking backwards compatibility, we will seek to provide an easy path for migrating P416 programs to the new version.
4. Architecture Model
The P4 architecture identifies the P4-programmable blocks (e.g., parser, ingress control flow, egress control flow, deparser, etc.) and their data plane interfaces.
The P4 architecture can be thought of as a contract between the program and the target. Each manufacturer must therefore provide both a P4 compiler as well as an accompanying architecture definition for their target. (We expect that P4 compilers can share a common front-end that handles all architectures). The architecture definition does not have to expose the entire programmable surface of the data plane—a manufacturer may even choose to provide multiple definitions for the same hardware device, each with different capabilities (e.g., with or without multicast support).
Figure 4 illustrates the data plane interfaces between P4-programmable blocks. It shows a target that has two programmable blocks (#1 and #2). Each block is programmed through a separate fragment of P4 code. The target interfaces with the P4 program through a set of control registers or signals. Input controls provide information to P4 programs (e.g., the input port that a packet was received from), while output controls can be written to by P4 programs to influence the target behavior (e.g., the output port where a packet has to be directed). Control registers/signals are represented in P4 as intrinsic metadata. P4 programs can also store and manipulate data pertaining to each packet as user-defined metadata.
The behavior of a P4 program can be fully described in terms of transformations that map vectors of bits to vectors of bits. To actually process a packet, the architecture model interprets the bits that the P4 program writes to intrinsic metadata. For example, to cause a packet to be forwarded on a specific output port, a P4 program may need to write the index of an output port into a dedicated control register. Similarly, to cause a packet to be dropped, a P4 program may need to set a "drop" bit into another dedicated control register. Note that the details of how intrinsic metadata are interpreted is architecture-specific.
P4 programs can invoke services implemented by extern objects and functions provided by the architecture. Figure 5 depicts a P4 program invoking the services of a built-in checksum computation unit on a target. The implementation of the checksum unit is not specified in P4, but its interface is. In general, the interface for an extern object describes each operation it provides, as well as their parameter and return types.
In general, P4 programs are not expected to be portable across different architectures. For example, executing a P4 program that broadcasts packets by writing into a custom control register will not function correctly on a target that does not have the control register. However, P4 programs written for a given architecture should be portable across all targets that faithfully implement the corresponding model, provided there are sufficient resources.
4.1. Standard architectures
We expect that the P4 community will evolve a small set of standard architecture models pertaining to specific verticals. Wide adoption of such standard architectures will promote portability of P4 programs across different targets. However, defining these standard architectures is outside of the scope of this document.
4.2. Data plane interfaces
To describe a functional block that can be programmed in P4, the architecture includes a type declaration that specifies the interfaces between the block and the other components in the architecture. For example, the architecture might contain a declaration such as the following:
control MatchActionPipe<H>(in bit<4> inputPort,
inout H parsedHeaders,
out bit<4> outputPort);
This type declaration describes a block named MatchActionPipe that can be
programmed using a data-dependent sequence of match-action unit invocations and
other imperative constructs (indicated by the control keyword). The
interface between the MatchActionPipe block and the other components of the
architecture can be read off from this declaration:
-
The first parameter is a 4-bit value named
inputPort.The directioninindicates that this parameter is an input that cannot be modified. -
The second parameter is an object of type
HnamedparsedHeaders, whereHis a type variable representing the headers that will be defined later by the P4 programmer. The directioninoutindicates that this parameter is both an input and an output. -
The third parameter is a 4-bit value named
outputPort. The directionoutindicates that this parameter is an output whose value is undefined initially but can be modified.
4.3. Extern objects and functions
P4 programs can also interact with objects and functions provided by the
architecture. Such objects are described using the extern construct, which
describes the interfaces that such objects expose to the data-plane.
An extern object describes a set of methods that are implemented by an
object, but not the implementation of these methods (i.e., it is similar to an
abstract class in an object-oriented language). For example, the following
construct could be used to describe the operations offered by an incremental
checksum unit:
extern Checksum16 {
Checksum16(); // constructor
void clear(); // prepare unit for computation
void update<T>(in T data); // add data to checksum
void remove<T>(in T data); // remove data from existing checksum
bit<16> get(); // get the checksum for the data added since last clear
}
5. Example: A very simple switch
As an example to illustrate the features of architectures, consider implementing a very simple switch in P4. We will first describe the architecture of the switch and then write a complete P4 program that specifies the data plane behavior of the switch. This example demonstrates many important features of the P4 programming language.
We call our architecture the "Very Simple Switch" (VSS). Figure 6 is a diagram of this architecture. There is nothing inherently special about VSS—it is just a pedagogical example that illustrates how programmable switches can be described and programmed in P4. VSS has a number of fixed-function blocks (shown in cyan in our example), whose behavior is described in Section 5.2. The white blocks are programmable using P4.
VSS receives packets through one of 8 input Ethernet ports, through a recirculation channel, or from a port connected directly to the CPU. VSS has one single parser, feeding into a single match-action pipeline, which feeds into a single deparser. After exiting the deparser, packets are emitted through one of 8 output Ethernet ports or one of 3 "special" ports:
-
Packets sent to the "CPU port" are sent to the control plane
-
Packets sent to the "Drop port" are discarded
-
Packets sent to the "Recirculate port" are re-injected in the switch through a special input port
The white blocks in the figure are programmable, and the user must provide a corresponding P4 program to specify the behavior of each such block. The red arrows indicate the flow of user-defined data. The cyan blocks are fixed-function components. The green arrows are data plane interfaces used to convey information between the fixed-function blocks and the programmable blocks—exposed in the P4 program as intrinsic metadata.
5.1. Very Simple Switch Architecture
The following P4 program provides a declaration of VSS in P4, as it would be provided by the VSS manufacturer. The declaration contains several type declarations, constants, and finally declarations for the three programmable blocks; the code uses syntax highlighting. The programmable blocks are described by their types; the implementation of these blocks has to be provided by the switch programmer.
// File "very_simple_switch_model.p4"
// Very Simple Switch P4 declaration
// core library needed for packet_in and packet_out definitions
# include <core.p4>
/* Various constants and structure declarations */
/* ports are represented using 4-bit values */
typedef bit<4> PortId;
/* only 8 ports are "real" */
const PortId REAL_PORT_COUNT = 4w8; // 4w8 is the number 8 in 4 bits
/* metadata accompanying an input packet */
struct InControl {
PortId inputPort;
}
/* special input port values */
const PortId RECIRCULATE_IN_PORT = 0xD;
const PortId CPU_IN_PORT = 0xE;
/* metadata that must be computed for outgoing packets */
struct OutControl {
PortId outputPort;
}
/* special output port values for outgoing packet */
const PortId DROP_PORT = 0xF;
const PortId CPU_OUT_PORT = 0xE;
const PortId RECIRCULATE_OUT_PORT = 0xD;
/* Prototypes for all programmable blocks */
/**
* Programmable parser.
* @param <H> type of headers; defined by user
* @param b input packet
* @param parsedHeaders headers constructed by parser
*/
parser Parser<H>(packet_in b,
out H parsedHeaders);
/**
* Match-action pipeline
* @param <H> type of input and output headers
* @param headers headers received from the parser and sent to the deparser
* @param parseError error that may have surfaced during parsing
* @param inCtrl information from architecture, accompanying input packet
* @param outCtrl information for architecture, accompanying output packet
*/
control Pipe<H>(inout H headers,
in error parseError,// parser error
in InControl inCtrl,// input port
out OutControl outCtrl); // output port
/**
* VSS deparser.
* @param <H> type of headers; defined by user
* @param b output packet
* @param outputHeaders headers for output packet
*/
control Deparser<H>(inout H outputHeaders,
packet_out b);
/**
* Top-level package declaration - must be instantiated by user.
* The arguments to the package indicate blocks that
* must be instantiated by the user.
* @param <H> user-defined type of the headers processed.
*/
package VSS<H>(Parser<H> p,
Pipe<H> map,
Deparser<H> d);
// Architecture-specific objects that can be instantiated
// Checksum unit
extern Checksum16 {
Checksum16(); // constructor
void clear(); // prepare unit for computation
void update<T>(in T data); // add data to checksum
void remove<T>(in T data); // remove data from existing checksum
bit<16> get(); // get the checksum for the data added since last clear
}
Let us describe some of these elements:
-
The included file
core.p4is described in more detail in Appendix D. It defines some standard data-types and error codes. -
bit<4>is the type of bit-strings with 4 bits. -
The syntax
4w0xFindicates the value 15 represented using 4 bits. An alternative notation is4w15. In many circumstances the width modifier can be omitted, writing just15. -
erroris a built-in P4 type for holding error codes -
Next follows the declaration of a parser:
parser Parser<H>(packet_in b, out H parsedHeaders);This declaration describes the interface for a parser, but not yet its implementation, which will be provided by the programmer. The parser reads its input from a
packet_in, which is a pre-defined P4 extern object that represents an incoming packet, declared in thecore.p4library. The parser writes its output (theoutkeyword) into theparsedHeadersargument. The type of this argument isH, yet unknown—it will also be provided by the programmer. -
The declaration
control Pipe<H>(inout H headers, in error parseError, in InControl inCtrl, out OutControl outCtrl);describes the interface of a Match-Action pipeline named
Pipe.
The pipeline receives three inputs: the headers headers, a parser error
parseError, and the inCtrl control data. Figure 6 indicates the
different sources of these pieces of information. The pipeline writes its
outputs into outCtrl, and it must update in place the headers to be consumed
by the deparser.
-
The top-level package is called
VSS; in order to program a VSS, the user will have to instantiate a package of this type (shown in the next section). The top-level package declaration also depends on a type variableH:package VSS<H>
A type variable indicates a type yet unknown that must be provided by the user
at a later time. In this case H is the type of the set of headers that the
user program will be processing; the parser will produce the parsed
representation of these headers, and the match-action pipeline will update the
input headers in place to produce the output headers.
-
The
package VSSdeclaration has three complex parameters, of typesParser,Pipe, andDeparserrespectively; which are exactly the declarations we have just described. In order to program the target one has to supply values for these parameters. -
In this program the
inCtrlandoutCtrlstructures represent control registers. The content of the headers structure is stored in general-purpose registers. -
The
extern Checksum16declaration describes an extern object whose services can be invoked to compute checksums.
5.2. Very Simple Switch Architecture Description
In order to fully understand VSS’s behavior and write meaningful P4 programs
for it, and for implementing a control plane, we also need a full behavioral
description of the fixed-function blocks. This section can be seen as a simple
example illustrating all the details that have to be handled when writing an
architecture description. The P4 language is not intended to cover the
description of all such functional blocks—the language can only describe the
interfaces between programmable blocks and the architecture. For the current
program, this interface is given by the Parser, Pipe, and Deparser
declarations. In practice we expect that the complete description of the
architecture will be provided as an executable program and/or diagrams and
text; in this document we will provide informal descriptions in English.
5.2.1. Arbiter block
The input arbiter block performs the following functions:
-
It receives packets from one of the physical input Ethernet ports, from the control plane, or from the input recirculation port.
-
For packets received from Ethernet ports, the block computes the Ethernet trailer checksum and verifies it. If the checksum does not match, the packet is discarded. If the checksum does match, it is removed from the packet payload.
-
Receiving a packet involves running an arbitration algorithm if multiple packets are available.
-
If the arbiter block is busy processing a previous packet and no queue space is available, input ports may drop arriving packets, without indicating the fact that the packets were dropped in any way.
-
After receiving a packet, the arbiter block sets the
inCtrl.inputPortvalue that is an input to the match-action pipeline with the identity of the input port where the packet originated. Physical Ethernet ports are numbered 0 to 7, while the input recirculation port has a number 13 and the CPU port has the number 14.
5.2.2. Parser runtime block
The parser runtime block works in concert with the parser. It provides an error code to the match-action pipeline, based on the parser actions, and it provides information about the packet payload (e.g., the size of the remaining payload data) to the demux block. As soon as a packet’s processing is completed by the parser, the match-action pipeline is invoked with the associated metadata as inputs (packet headers and user-defined metadata).
5.2.3. Demux block
The core functionality of the demux block is to receive the headers for the
outgoing packet from the deparser and the packet payload from the parser, to
assemble them into a new packet and to send the result to the correct output
port. The output port is specified by the value of outCtrl.ouputPort, which
is set by the match-action pipeline.
-
Sending the packet to the drop port causes the packet to disappear.
-
Sending the packet to an output Ethernet port numbered between 0 and 7 causes it to be emitted on the corresponding physical interface. The packet may be placed in a queue if the output interface is already busy emitting another packet. When the packet is emitted, the physical interface computes a correct Ethernet checksum trailer and appends it to the packet.
-
Sending a packet to the output CPU port causes the packet to be transferred to the control plane. In this case, the packet that is sent to the CPU is the original input packet, and not the packet received from the deparser—the latter packet is discarded.
-
Sending the packet to the output recirculation port causes it to appear at the input recirculation port. Recirculation is useful when packet processing cannot be completed in a single pass.
-
If the
outputPorthas an illegal value (e.g., 9), the packet is dropped. -
Finally, if the demux unit is busy processing a previous packet and there is no capacity to queue the packet coming from the deparser, the demux unit may drop the packet, irrespective of the output port indicated.
Please note that some of the behaviors of the demux block may be unexpected—we have highlighted them in bold. We are not specifying here several important behaviors related to queue size, arbitration, and timing, which also influence the packet processing.
The arrow shown from the parser runtime to the demux block represents an additional information flow from the parser to the demux: the packet being processed as well as the offset within the packet where parsing ended (i.e., the start of the packet payload).
5.2.4. Available extern blocks
The VSS architecture provides an incremental checksum extern block, called
Checksum16. The checksum unit has a constructor and four methods:
-
clear(): prepares the unit for a new computation -
update<T>(in T data): add some data to be checksummed. The data must be either a bit-string, a header-typed value, or astructcontaining such values. The fields in the header/struct are concatenated in the order they appear in the type declaration. -
get(): returns the 16-bit one’s complement checksum. When this function is invoked the checksum must have received an integral number of bytes of data. -
remove<T>(in T data): assuming thatdatawas used for computing the current checksum,datais removed from the checksum.
5.3. A complete Very Simple Switch program
Here we provide a complete P4 program that implements basic forwarding for IPv4
packets on the VSS architecture. This program does not utilize all of the
features provided by the architecture—e.g., recirculation—but it does use
preprocessor #include directives (see [sec-preprocessor]).
The parser attempts to recognize an Ethernet header followed by an IPv4 header.
If either of these headers are missing, parsing terminates with an error.
Otherwise it extracts the information from these headers into a Parsed_packet
structure. The match-action pipeline is shown in Figure 7; it comprises
four match-action units (represented by the P4 table keyword):
-
If any parser error has occurred, the packet is dropped (i.e., by assigning
-
outputPorttoDROP_PORT) -
The first table uses the IPv4 destination address to determine the
-
outputPortand the IPv4 address of the next hop. If this lookup fails, the packet is dropped. The table also decrements the IPv4ttlvalue. -
The second table checks the
ttlvalue: if thettlbecomes 0, the packet is sent to the control plane through the CPU port. -
The third table uses the IPv4 address of the next hop (which was computed by the first table) to determine the Ethernet address of the next hop.
-
Finally, the last table uses the
outputPortto identify the source Ethernet address of the current switch, which is set in the outgoing packet.
The deparser constructs the outgoing packet by reassembling the Ethernet and IPv4 headers as computed by the pipeline.
// Include P4 core library
# include <core.p4>
// Include very simple switch architecture declarations
# include "very_simple_switch_model.p4"
// This program processes packets comprising an Ethernet and an IPv4
// header, and it forwards packets using the destination IP address
typedef bit<48> EthernetAddress;
typedef bit<32> IPv4Address;
// Standard Ethernet header
header Ethernet_h {
EthernetAddress dstAddr;
EthernetAddress srcAddr;
bit<16> etherType;
}
// IPv4 header (without options)
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;
IPv4Address srcAddr;
IPv4Address dstAddr;
}
// Structure of parsed headers
struct Parsed_packet {
Ethernet_h ethernet;
IPv4_h ip;
}
// Parser section
// User-defined errors that may be signaled during parsing
error {
IPv4OptionsNotSupported,
IPv4IncorrectVersion,
IPv4ChecksumError
}
parser TopParser(packet_in b, out Parsed_packet p) {
Checksum16() ck; // instantiate checksum unit
state start {
b.extract(p.ethernet);
transition select(p.ethernet.etherType) {
0x0800: parse_ipv4;
// no default rule: all other packets rejected
}
}
state parse_ipv4 {
b.extract(p.ip);
verify(p.ip.version == 4w4, error.IPv4IncorrectVersion);
verify(p.ip.ihl == 4w5, error.IPv4OptionsNotSupported);
ck.clear();
ck.update(p.ip);
// Verify that packet checksum is zero
verify(ck.get() == 16w0, error.IPv4ChecksumError);
transition accept;
}
}
// Match-action pipeline section
control TopPipe(inout Parsed_packet headers,
in error parseError, // parser error
in InControl inCtrl, // input port
out OutControl outCtrl) {
IPv4Address nextHop; // local variable
/**
* Indicates that a packet is dropped by setting the
* output port to the DROP_PORT
*/
action Drop_action() {
outCtrl.outputPort = DROP_PORT;
}
/**
* Set the next hop and the output port.
* Decrements ipv4 ttl field.
* @param ipv4_dest ipv4 address of next hop
* @param port output port
*/
action Set_nhop(IPv4Address ipv4_dest, PortId port) {
nextHop = ipv4_dest;
headers.ip.ttl = headers.ip.ttl - 1;
outCtrl.outputPort = port;
}
/**
* Computes address of next IPv4 hop and output port
* based on the IPv4 destination of the current packet.
* Decrements packet IPv4 TTL.
* @param nextHop IPv4 address of next hop
*/
table ipv4_match {
key = { headers.ip.dstAddr: lpm; } // longest-prefix match
actions = {
Drop_action;
Set_nhop;
}
size = 1024;
default_action = Drop_action;
}
/**
* Send the packet to the CPU port
*/
action Send_to_cpu() {
outCtrl.outputPort = CPU_OUT_PORT;
}
/**
* Check packet TTL and send to CPU if expired.
*/
table check_ttl {
key = { headers.ip.ttl: exact; }
actions = { Send_to_cpu; NoAction; }
const default_action = NoAction; // defined in core.p4
}
/**
* Set the destination MAC address of the packet
* @param dmac destination MAC address.
*/
action Set_dmac(EthernetAddress dmac) {
headers.ethernet.dstAddr = dmac;
}
/**
* Set the destination Ethernet address of the packet
* based on the next hop IP address.
* @param nextHop IPv4 address of next hop.
*/
table dmac {
key = { nextHop: exact; }
actions = {
Drop_action;
Set_dmac;
}
size = 1024;
default_action = Drop_action;
}
/**
* Set the source MAC address.
* @param smac: source MAC address to use
*/
action Set_smac(EthernetAddress smac) {
headers.ethernet.srcAddr = smac;
}
/**
* Set the source mac address based on the output port.
*/
table smac {
key = { outCtrl.outputPort: exact; }
actions = {
Drop_action;
Set_smac;
}
size = 16;
default_action = Drop_action;
}
apply {
if (parseError != error.NoError) {
Drop_action(); // invoke drop directly
return;
}
ipv4_match.apply(); // Match result will go into nextHop
if (outCtrl.outputPort == DROP_PORT) return;
check_ttl.apply();
if (outCtrl.outputPort == CPU_OUT_PORT) return;
dmac.apply();
if (outCtrl.outputPort == DROP_PORT) return;
smac.apply();
}
}
// deparser section
control TopDeparser(inout Parsed_packet p, packet_out b) {
Checksum16() ck;
apply {
b.emit(p.ethernet);
if (p.ip.isValid()) {
ck.clear(); // prepare checksum unit
p.ip.hdrChecksum = 16w0; // clear checksum
ck.update(p.ip); // compute new checksum.
p.ip.hdrChecksum = ck.get();
}
b.emit(p.ip);
}
}
// Instantiate the top-level VSS package
VSS(TopParser(),
TopPipe(),
TopDeparser()) main;
6. P4 language definition
This section provides a high-level overview of the P4 programming language, focusing on its syntax, semantics, lexical constructs, scoping rules, calling conventions, and naming conventions.
Chapter 7 introduces the P4 abstract machine, which serves as the model for describing the behavior of P4 programs. Chapter 8 through Chapter 16 define the P4 syntax and semantics in detail.
6.1. Syntax and semantics
6.1.1. Grammar
The complete grammar of P416 is given in Appendix E, using Yacc/Bison grammar description language. This text is based on the same grammar. We adopt several standard conventions when we provide excerpts from the grammar:
-
UPPERCASEsymbols denote terminals in the grammar. -
Excerpts from the grammar are given in BNF notation as follows:
p4program
: /* empty */
| p4program declaration
| p4program ;
;
6.1.2. Semantics and the P4 abstract machines
We describe the semantics of P4 in terms of abstract machines executing traditional imperative code.
P4 compilers are free to reorganize the code they generate in any way as long as the externally visible behaviors of the P4 programs are preserved as described by this specification where externally visible behavior is defined as:
-
The input/output behavior of all P4 blocks, and
-
The state maintained by extern blocks.
Throughout this specification, the P4 semantics is described using a combination of:
-
P4 code snippets
-
Pseudo-code snippets
-
Prose algorithms
-
Formal inference rules
6.1.2.1. Pseudo-code snippets
Pseudo-code (mostly used for describing the semantics of various P4 constructs) are shown with fixed-size fonts as in the following example:
ParserModel.verify(bool condition, error err) {
if (condition == false) {
ParserModel.parserError = err;
goto reject;
}
}
6.1.2.2. Prose algorithms
The semantics of P4 constructs are also described using prose algorithms. For example, below is the signature of an algorithm used to describe type checking of P4 expressions.
Detailed discussion of what cursor, typingContext, and
typedExpressionIR mean is given in Section 7.2. For now, it
suffices to know that Expr_ok takes an expression, a cursor, and a
typingContext as inputs and results in a typedExpressionIR.
An algorithm is defined in terms of case-analysis, where each case corresponds to a variant of a P4 construct. For example, the following algorithm defines how to type check logical not expressions.
The shorthand ! in step 2 is explained in Section 6.1.2.2.1 and
$compat_lnot is a helper function used to check compatibility of the
operand with logical not expressions. And the following algorithm defines
type checking of bitwise negation expressions.
And so on for other variants of P4 expressions. These prose algorithms are presented throughout this specification.
6.1.2.2.1. Prose algorithm shorthands
To make the prose algorithms concise, we often use the shorthand !. Some algorithms result in an optional result to indicate possible failure. The following example algorithm computes modulo of two natural numbers (including zero).
Because the divisor may be zero, the result is optional. Now consider the case when the algorithm is called.
Here, ! indicates unwrapping of the optional result. This shortened form is
equivalent to first getting the optional result, checking that it is not
None, and then unwrapping it.
6.1.2.3. Formal inference rules
Alongside prose algorithms, we also present formal inference rules. In fact, the prose algorithms are automatically generated from the formal inference rules. Although the prose algorithms are enough to understand the P4 semantics, the formal inference rules are included to aid comprehension.
For example, the following formal inference rule defines type checking of logical not expressions.
Click to view the specification source
rulegroup Expr_ok/unaryExpression-lnot: rule Expr_ok/unaryExpression-lnot: p TC |- ! expression : (! typedExpressionIR_reduced) # expressionNoteIR -- Expr_ok: p TC |- expression : typedExpressionIR -- if typedExpressionIR_reduced = $reduce_serenum_unary(typedExpressionIR, $compat_lnot) -- if typeIR_reduced = $type_of_typedExpressionIR(typedExpressionIR_reduced) -- if ctk_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_reduced) -- if expressionNoteIR = `(typeIR_reduced ctk_reduced)
It is defined using mathematical notations such as turnstile (|-). The lines
leading with -- are called premises.
6.2. Lexical constructs
All P4 keywords use only ASCII characters. All P4 identifiers must use only ASCII characters. P4 compilers should handle correctly strings containing 8-bit characters in comments and string literals. P4 is case-sensitive. Whitespace characters, including newlines are treated as token separators. Indentation is free-form; however, P4 has C-like block constructs, and all our examples use C-style indentation. Tab characters are treated as spaces.
The lexer recognizes the following kinds of terminals:
-
IDENTIFIER: start with a letter or underscore, and contain letters, digits and underscores -
TYPE_IDENTIFIER: identifier that denotes a type name -
INTEGER: integer literals -
DONTCARE: a single underscore -
Keywords such as
RETURN. By convention, each keyword terminal corresponds to a language keyword with the same spelling but using lowercase. For example, theRETURNterminal corresponds to thereturnkeyword.
6.2.1. Identifiers
P4 identifiers may contain only letters, numbers, and the underscore character
_, and must start with a letter or underscore. The special identifier
consisting of a single underscore _ is reserved to indicate a "don’t care"
value; its type may vary depending on the context. Certain keywords (e.g.,
apply) can be used as identifiers if the context makes it unambiguous.
identifier
: `ID text
;
nonTypeName
: identifier
| APPLY
| KEY
| ACTIONS
| STATE
| ENTRIES
| TYPE
| PRIORITY
;
typeIdentifier
: `TID text
;
typeName = typeIdentifier
name
: nonTypeName
| typeName
| LIST
;
6.2.2. Literal constants
6.2.2.1. Boolean literals
booleanLiteral
: TRUE
| FALSE
;
There are two Boolean literal constants: true and false.
6.2.2.2. Integer literals
integerLiteral
: D int
| nat W int
| nat S int
;
Integer literals are non-negative arbitrary-precision integers. By default, literals are represented in base 10. The following prefixes must be employed to specify the base explicitly:
-
0xor0Xindicates base 16 (hexadecimal) -
0oor0Oindicates base 8 (octal) -
0dor0Dindicates base 10 (decimal) -
0bor0Bindicates base 2
The width of a numeric literal in bits can be specified by an unsigned number prefix consisting of a number of bits and a signedness indicator:
-
windicates unsigned numbers -
sindicates signed numbers
Note that a leading zero by itself does not indicate an octal (base 8) constant. The underscore character is considered a digit within number literals but is ignored when computing the value of the parsed number. This allows long constant numbers to be more easily read by grouping digits together. The underscore cannot be used in the width specification or as the first character of an integer literal. No comments or whitespaces are allowed within a literal. Here are some examples of numeric literals:
32w255 // a 32-bit unsigned number with value 255
32w0d255 // same value as above
32w0xFF // same value as above
32s0xFF // a 32-bit signed number with value 255
8w0b10101010 // an 8-bit unsigned number with value 0xAA
8w0b_1010_1010 // same value as above
8w170 // same value as above
8s0b1010_1010 // an 8-bit signed number with value -86
16w0377 // 16-bit unsigned number with value 377 (not 255!)
16w0o377 // 16-bit unsigned number with value 255 (base 8)
6.2.2.3. String literals
stringLiteral
: " text "
;
String literals are specified as an arbitrary sequence of 8-bit characters,
enclosed within double quote characters " (ASCII code 34). Strings start with
a double quote character and extend to the first double quote sign which is not
immediately preceded by an odd number of backslash characters (ASCII code 92).
P4 does not make any validity checks on strings (i.e., it does not check that
strings represent legal UTF-8 encodings).
Since P4 does not allow strings to exist at runtime, string literals are generally passed unchanged through the P4 compiler to other third-party tools or compiler-backends. The compiler can, however, perform compile-time concatenation (constant-folding) of concatenation expressions into single literal. When such concatenation is performed, the binary representation of the string literals (excluding the quotes) is concatenated in the order they appear in the source code. There are no escape sequences that would be treated specially when strings are concatenated.
The backends and other tools can define their own handling of escape sequences (e.g., how to specify Unicode characters, or handle unprintable ASCII characters).
Here are 3 examples of string literals:
"simple string"
"string \" with \" embedded \" quotes"
"string with embedded
line terminator"
Here is an example of concatenation expression and an equivalent string literal:
"one string \" with a quote inside;" ++ (" " ++ "another string")
// can be constant folded to
"one string \" with a quote inside; another string"
6.2.3. Comments
P4 supports several kinds of comments:
-
Single-line comments, introduced by
//and spanning to the end of line, -
Multi-line comments, enclosed between
/*and*/ -
Nested multi-line comments are not supported.
-
Javadoc-style comments, starting with
/**and ending with*/
Use of Javadoc-style comments is strongly encouraged for the tables and actions that are used to synthesize the interface with the control-plane.
P4 treats comments as token separators and no comments are allowed within a
token---e.g. bi/**/t is parsed as two tokens, bi and t, and not as a
single token bit.
6.2.4. Optional trailing commas
The P4 grammar allows several kinds of comma-separated lists to end in an optional comma.
trailingCommaOpt
: /* empty */
| ,
;
For example, the following declarations are both legal, and have the same meaning:
enum E {
a, b, c
}
enum E {
a, b, c,
}
This is particularly useful in combination with preprocessor directives:
enum E {
#if SUPPORT_A
a,
#endif
b,
c,
}
6.3. Scoping
Scopes are used to organize P4 programs into nested namespaces. There are three
kinds of scopes in P4, from outermost to innermost: the top-level scope (global
namespace), the scope within a parser, control, or extern declaration
(block namespace), and the scope within a block statement (local namespace).
Local namespaces can be nested within other local namespaces.
// top-level (global) scope
const bit<8> GLOBAL = 255;
control c() {
// block scope within a control
bit<8> block = GLOBAL;
apply {
// local scope
bit<8> local = block;
}
}
bit<8> f() {
// local scope
bit<8> local = GLOBAL;
{
// nested local scope
bit<8> local_nested = local;
}
return local;
}
Cursors are used to track scopes while defining the semantic rules of P4.
cursor
: GLOBAL
| BLOCK
| LOCAL
;
6.3.1. Name resolution
P4 namespaces are organized in a hierarchical fashion.
prefixedNonTypeName
: nonTypeName
| `ID . nonTypeName
;
prefixedTypeName
: typeName
| `TID . typeName
;
Identifiers prefixed with a dot are always resolved in the top-level namespace.
const bit<32> x = 2;
control c() {
int<32> x = 0;
apply {
x = x + (int<32>).x; // x is the int<32> local variable,
// .x is the top-level bit<32> variable
}
}
References to resolve an identifier are attempted inside-out, starting with the current scope and proceeding to all lexically enclosing scopes. The compiler may provide a warning if multiple resolutions are possible for the same name (name shadowing).
const bit<4> x = 1;
control c() {
const bit<8> x = 8; // x declaration shadows global x
const bit<4> y = .x; // reference to top-level x
const bit<8> z = x; // reference to c's local x
apply {}
}
The order of declarations is important; with the exception of parser states, all uses of a symbol must follow the symbol’s declaration. (This is a departure from P414, which allows declarations in any order. This requirement significantly simplifies the implementation of compilers for P4, allowing compilers to use additional information about declared identifiers to resolve ambiguities.)
See Section 14.3 for how variable references are resolved with respect to scopes.
6.3.2. Name visibility
Identifiers defined in the top-level namespace are globally visible.
Declarations within a parser or control are private and cannot be referred
to from outside of the enclosing parser or control.
6.4. Stateful elements
Most P4 constructs are stateless: given some inputs they produce a result that solely depends on these inputs. There are only two stateful constructs that may retain information across packets:
-
tables: Tables are read-only for the data plane, but their entries can be modified by the control-plane, -
externobjects: many objects have state that can be read and written by the control plane and data plane. All constructs from the P414 language version that encapsulate state (e.g., counters, meters, registers) are represented usingexternobjects in P416.
In P4 all stateful elements must be explicitly allocated at compilation-time through the process called "instantiation".
In addition, parsers, control blocks, and packages may contain
stateful element instantiations. Thus, they are also treated as stateful
elements, even if they appear to contain no state, and must be instantiated
before they can be used. However, although they are stateful, tables do not
need to be instantiated explicitly—declaring a table also creates an
instance of it. This convention is designed to support the common case, since
most tables are used just once. To have finer-grained control over when a
table is instantiated, a programmer can declare it within a control.
Recall the example in Section 5.3: TopParser, TopPipe, TopDeparser,
Checksum16, and Switch are types. There are two instances of Checksum16,
one in TopParser and one in TopDeparser, both called ck. The TopParser,
TopDeparser, TopPipe, and Switch are instantiated at the end of the
program, in the declaration of the main instance object, which is an instance
of the Switch type (a package).
6.5. Call convention: copy-in/copy-out
P4 provides multiple constructs for writing modular programs: extern methods, parsers, controls, actions. All these constructs behave similarly to procedures in standard general-purpose programming languages:
-
They have named and typed parameters.
-
They introduce a new local scope for parameters and local variables.
-
They allow arguments to be passed by binding them to their parameters.
parameterList
: /* empty */
| nonEmptyParameterList
;
nonEmptyParameterList
: parameter
| nonEmptyParameterList , parameter
;
parameter
: annotationList direction type name initializerOpt
;
direction
: /* empty */
| IN
| OUT
| INOUT
;
Invocations are executed using copy-in/copy-out semantics.
Each parameter may be labeled with a direction:
-
inparameters are read-only. It is an error to use aninparameter on the left-hand side of an assignment or to pass it to a callee as a non-inargument.inparameters are initialized by copying the value of the corresponding argument when the invocation is executed. -
outparameters are, with a few exceptions listed below, uninitialized and are treated as l-values (See Chapter 12) within the body of the method or function. An argument passed as anoutparameter must be an l-value; after the execution of the call, the value of the parameter is copied to the corresponding storage location for that l-value. -
inoutparameters behave like a combination ofinandoutparameters simultaneously: On entry the value of the arguments is copied to the parameters. On return the value of the parameters is copied back to the arguments. In consequence, an argument passed as aninoutparameter must be an l-value. -
The meaning of parameters with no direction depends upon the kind of entity the parameter is for:
-
For anything other than an action, e.g. a control, parser, or function, a directionless parameter means that the value supplied as an argument in a call must be a compile-time known value (see Section 7.5).
-
For an action, a directionless parameter indicates that it is "action data". See Section 9.3 for the meaning of action data, but its meaning includes the following possibilities:
-
The parameter’s value is provided in the P4 program. In this case, the parameter behaves as if the direction were
in. Such an argument expression need not be a compile-time known value. -
The parameter’s value is provided by the control plane software when an entry is added to a table that uses that action. See Section 9.3.
-
-
A directionless parameter of an extern object type is passed by reference.
-
Direction out parameters are always initialized at the beginning of execution
of the portion of the program that has the out parameters, e.g. control,
parser, action, function, etc. This initialization is not performed for
parameters with any direction that is not out.
-
If a direction
outparameter is of typeheaderorheader_union, it is set to "invalid". -
If a direction
outparameter is of type header stack, all elements of the header stack are set to "invalid", and itsnextIndexfield is initialized to 0 (see Section 8.4.3). -
If a direction
outparameter is a compound type, e.g. a struct or tuple, other than one of the types listed above, then apply these rules recursively to its members. -
If a direction
outparameter has any other type, e.g.bit<W>, an implementation need not initialize it to any predictable value.
For example, if a direction out parameter has type s2_t named p:
header h1_t {
bit<8> f1;
bit<8> f2;
}
struct s1_t {
h1_t h1a;
bit<3> a;
bit<7> b;
}
struct s2_t {
h1_t h1b;
s1_t s1;
bit<5> c;
}
then at the beginning of execution of the part of the program that has the
out parameter p, it must be initialized so that p.h1b and and p.s1.h1a
are invalid. No other parts of p are required to be initialized.
Arguments are evaluated from left to right prior to the invocation of the function itself. The order of evaluation is important when the expression supplied for an argument can have side-effects. Consider the following example:
extern void f(inout bit x, in bit y);
extern bit g(inout bit z);
bit a;
f(a, g(a));
Note that the evaluation of g may mutate its argument a, so the compiler
has to ensure that the value passed to f for its first parameter is not
changed by the evaluation of the second argument. The semantics for evaluating
a function call is given by the following algorithm (implementations can be
different as long as they provide the same result):
-
Arguments are evaluated from left to right as they appear in the function call expression.
-
If a parameter has a default value and no corresponding argument is supplied, the default value is used as an argument.
-
For each
outandinoutargument the corresponding l-value is saved (so it cannot be changed by the evaluation of the following arguments). This is important if the argument contains indexing operations into a header stack. -
The value of each argument is saved into a temporary.
-
The function is invoked with the temporaries as arguments. We are guaranteed that the temporaries that are passed as arguments are never aliased to each other, so this "generated" function call can be implemented using call-by-reference if supported by the architecture.
-
On function return, the temporaries that correspond to
outorinoutarguments are copied in order from left to right into the l-values saved in Step 3.
According to this algorithm, the previous function call is equivalent to the following sequence of statements:
bit tmp1 = a; // evaluate a; save result
bit tmp2 = g(a); // evaluate g(a); save result; modifies a
f(tmp1, tmp2); // evaluate f; modifies tmp1
a = tmp1; // copy inout result back into a
To see why Step 3 in the above algorithm is important, consider the following example:
header H { bit z; }
H[2] s;
f(s[a].z, g(a));
The evaluation of this call is equivalent to the following sequence of statements:
bit tmp1 = a; // save the value of a
bit tmp2 = s[tmp1].z; // evaluate first argument
bit tmp3 = g(a); // evaluate second argument; modifies a
f(tmp2, tmp3); // evaluate f; modifies tmp2
s[tmp1].z = tmp2; // copy inout result back; dest is not s[a].z
When used as arguments, extern objects can only be passed as directionless
parameters—e.g., see the packet argument in the very simple switch example.
6.5.1. Justification
The main reason for using copy-in/copy-out semantics (instead of the more
common call-by-reference semantics) is for controlling the side-effects of
extern functions and methods. extern methods and functions are the main
mechanism by which a P4 program communicates with its environment. With
copy-in/copy-out semantics extern functions cannot hold references to P4
program objects; this enables the compiler to limit the side-effects that
extern functions may have on the P4 program both in space (they can only
affect out parameters) and in time (side-effects can only occur at function
call time).
In general, extern functions are arbitrarily powerful: they can store
information in global storage, spawn separate threads, "collude" with each
other to share information—but they cannot access any variable in a P4
program. With copy-in/copy-out semantics the compiler can still reason about P4
programs that invoke extern functions.
There are additional benefits of using copy-in copy-out semantics:
-
It enables P4 to be compiled for architectures that do not support references (e.g., where all data is allocated to named registers. Such architectures may require indices into header stacks that appear in a program to be compile-time known values.)
-
It simplifies some compiler analyses, since function parameters can never alias to each other within the function body.
Following is a summary of the constraints imposed by the parameter directions:
-
When used as arguments, extern objects can only be passed as directionless parameters.
-
All constructor parameters are evaluated at compilation-time, and in consequence they must all be directionless (they cannot be
in,out, orinout); this applies topackage,control,parser, andexternobjects. Expressions for these parameters must be supplied at compile-time, and they must evaluate to compile-time known values. See Section 10.2.1 for further details. -
For actions all directionless parameters must be at the end of the parameter list. When an action appears in a
table'sactionslist, only the parameters with a direction must be bound. See Section 9.3 for further details. -
Actions can also be explicitly invoked using function call syntax, either from a control block or from another action. In this case, values for all action parameters must be supplied explicitly, including values for the directionless parameters. The directionless parameters in this case behave like
inparameters. See Section 18.6.1 for further details. -
Default expressions are only allowed for
inor directionless parameters, and the expressions supplied as defaults must be compile-time known values. -
If parameters with default values do not appear at the end of the list of parameters, invocations that use the default values must use named arguments, as in the following example:
extern void f(in bit a, in bit<3> b = 2, in bit<5> c);
void g() {
f(a = 1, b = 2, c = 3); // ok
f(a = 1, c = 3); // ok, equivalent to the previous call, b uses default value
f(1, 2, 3); // ok, equivalent to the previous call
// f(1, 3); // illegal, since the parameter b is not the last in the list
}
6.5.2. Optional parameters
A parameter that is annotated with the @optional annotation is optional: the
user may omit the value for that parameter in an invocation. Optional
parameters can only appear for arguments of: packages, parser types, control
types, extern functions, extern methods, and extern object constructors.
Optional parameters cannot have default values. If a procedure-like construct
has both optional parameters and default values then it can only be called
using named arguments. It is recommended, but not mandatory, for all optional
parameters to be at the end of a parameter list.
The implementation of such objects is not expressed in P4, so the meaning and implementation of optional parameters should be specified by the target architecture. For example, we can imagine a two-stage switch architecture where the second stage is optional. This could be declared as a package with an optional parameter:
package pipeline(/* parameters omitted */);
package switch(pipeline first, @optional pipeline second);
pipeline(/* arguments omitted */) ingress;
switch(ingress) main; // a switch with a single-stage pipeline
Here the target architecture could implement the elided optional argument using an empty pipeline.
The following example shows optional parameters and parameters with default values.
extern void h(in bit<32> a, in bool b = true); // default value
// function calls
h(10); // same as h(10, true);
h(a = 10); // same as h(10, true);
h(a = 10, b = true);
struct Empty {}
control nothing(inout Empty h, inout Empty m) {
apply {}
}
parser parserProto<H, M>(packet_in p, out H h, inout M m);
control controlProto<H, M>(inout H h, inout M m);
package pack<HP, MP, HC, MC>(
@optional parserProto<HP, MP> _parser, // optional parameter
controlProto<HC, MC> _control = nothing()); // default parameter value
pack() main; // No value for _parser, _control is an instance of nothing()
6.6. Overloading
Functions, methods, and constructors 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, function, or constructor 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 abstract methods, parsers, controls, or packages is not allowed:
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) {
// ...
//}
See Section 18.2 for details on how overload is resolved.
6.7. Naming conventions
P4 provides a rich assortment of types. Base types include bit-strings, numbers, and errors. There are also built-in types for representing constructs such as parsers, pipelines, actions, and tables. Users can construct new types based on these: structures, enumerations, headers, header stacks, header unions, etc.
In this document we adopt the following conventions:
-
Built-in types are written with lowercase characters—e.g.,
int<20>, -
User-defined types are capitalized—e.g.,
IPv4Address, -
Type variables are always uppercase—e.g.,
parser P<H, IH>(), -
Variables are uncapitalized-- e.g.,
ipv4header, -
Constants are written with uppercase characters—e.g.,
CPU_PORT, and -
Errors and enumerations are written in camel-case-- e.g.
PacketTooShort.
7. The P4 Abstract Machine
A P416 program is a list of declarations:
p4program
: /* empty */
| p4program declaration
| p4program ;
;
An empty declarations is indicated with a single semicolon. (Allowing empty
declarations accommodates the habits of C/C++ and Java programmers—e.g.,
certain constructs, like struct, do not require a terminating semicolon).
The evaluation of a P4 program is done in four stages:
-
preprocessing: at compile time, the P4 program is preprocessed to handle directives such as
#includeand#define. -
type checking: at compile time, the P4 program is type checked according to the P4 type system.
-
instantiation: at compile time, all stateful blocks in the P4 program are instantiated.
-
runtime evaluation: at runtime, each P4 functional block is executed to completion, in isolation, when it receives control from the architecture.
The preprocessing stage is described in Section 7.1.
The type system is described in Section 7.2. In addition to checking the types, the type system also converts an input P4 program into an intermediate representation (P4IR). P4IR is an extension of the surface syntax of P4, e.g., the types of all expressions are made explicit, implicit type casts are inserted, and omitted type arguments are inferred. P4IR is described in Section 7.2.1.
The instantiation stage is described in Section 7.3. It traverses the type checked P4IR program, and recursively instantiates all stateful blocks. The result of the instantiation stage is a global store that holds all instantiated stateful blocks.
The runtime evaluation stage is described in Section 7.4. It describes how each P4 block, represented as stateful objects within the global store, is executed to completion. These blocks are orchestrated by the architecture to compose the overall packet processing pipeline.
7.1. Preprocessing
To aid the composition of programs from multiple source files, P4 compilers should support the following subset of the C preprocessor functionality:
-
#definefor defining macros (without arguments) -
#undef -
#if #else #endif #ifdef #ifndef #elif -
#include
The preprocessor should also remove the sequence backslash newline (ASCII codes 92, 10) to facilitate splitting content across multiple lines when convenient for formatting.
Additional C preprocessor capabilities may be supported, but are not
guaranteed---e.g., macros with arguments. Similar to C, #include can
specify a file name either within double quotes or within <>.
# include <system_file>
# include "user_file"
The difference between the two forms is the order in which the preprocessor searches for header files when the path is incompletely specified.
P4 compilers should correctly handle #line directives that may be generated
during preprocessing. This functionality allows P4 programs to be built from
multiple source files, potentially produced by different programmers at
different times:
-
the P4 core library, defined in this document,
-
the architecture, defining data plane interfaces and extern blocks,
-
user-defined libraries of useful components (e.g. standard protocol header definitions), and
-
the P4 programs that specify the behavior of each programmable block.
The P4 language specification defines a core library that includes several common programming constructs. A description of the core library is provided in Appendix D. All P4 programs must include the core library. Including the core library is done with
# include <core.p4>
7.2. Type checking
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.
7.2.1. P4 Intermediate Representation
P4IR is an intermediate representation for P4 programs. P4IR extends the surface syntax of P416 with additional information obtained during type checking. They include:
-
All expressions are annotated with their types.
-
Implicit casts are made explicit.
-
Omitted type arguments are inferred and made explicit.
P4IR is designed to stay as close as possible to the surface syntax of P416. This makes it easy to relate P4IR programs to their source P416 programs.
Take expressions as an example.
expression
: literalExpression
| referenceExpression
| defaultExpression
| unaryExpression
| binaryExpression
| ternaryExpression
| castExpression
| dataExpression
| accessExpression
| callExpression
| parenthesizedExpression
;
expressionIR
: literalExpressionIR
| referenceExpressionIR
| defaultExpressionIR
| unaryExpressionIR
| binaryExpressionIR
| ternaryExpressionIR
| castExpressionIR
| dataExpressionIR
| accessExpressionIR
| callExpressionIR
| parenthesizedExpressionIR
;
expressionNoteIR
: `( typeIR ctk )
;
typedExpressionIR
: expressionIR # expressionNoteIR
;
typedExpressionIR is an intermediate representation of expression that is
annotated with type information, expressionNoteIR. Nevertheless, the
structure of the grammar mostly stays the same.
binaryExpression
: expression binop expression
;
binaryExpressionIR
: typedExpressionIR binop typedExpressionIR
;
binaryExpressionIR is an intermediate representation of binaryExpression.
The only difference is that the operands are now of type typedExpressionIR.
7.2.2. Typing a P4 program
For a well-typed P416 program, p4program, a type checker produces a P4IR
program, p4programIR:
p4programIR
: declarationIR* ;
;
Program_ok type checks a p4program and produces a p4programIR.
Click to view the specification source
relation Program_ok: |- p4program : p4programIR
Below is a prose algorithm that implements Program_ok. Details of what each
meta-variable means are provided in the subsequent sections.
Click to view the specification source
rulegroup Program_ok: rule Program_ok: |- p4program : (declarationIR* ;) -- if declaration* = $flatten_p4program(p4program) -- if TC_0 = $empty_typingContext -- Decls_ok: TC_0 |- declaration* : TC_1 declarationIR*
The following helper function is used to collect declarations in a p4program:
Click to view the specification source
def $flatten_p4program(`EMPTY) = eps def $flatten_p4program(p4program declaration) = $flatten_p4program(p4program) ++ [declaration] def $flatten_p4program(p4program ;) = $flatten_p4program(p4program)
7.2.3. Typing context
globalTypingLayer = {
CONSTRUCTOR constructorTypeDefEnv,
TYPE typeDefEnv,
CALLABLE callableTypeDefEnv,
FRAME typeFrame
}
blockTypingLayer = {
KIND blockKind,
TYPE typeDefEnv,
CALLABLE callableTypeDefEnv,
FRAME typeFrame
}
localTypingLayer = {
KIND localKind,
TYPE typeDefEnv,
FRAMES typeFrame*
}
typingContext = {
GLOBAL globalTypingLayer,
BLOCK blockTypingLayer,
LOCAL localTypingLayer
}
A typing context is used to keep track of the types of all named declarations in scope at a given program point. It consists of three layers:
-
A global typing layer, which contains all declarations at the top level of a P416 program.
-
A block typing layer, which contains all declarations in the current block scope.
-
A local typing layer, which contains all declarations in the current local scope.
Each layer contains mappings from names to types.
varTypeIR
: direction typeIR ctk value?
;
typeFrame = map<id, varTypeIR>
typeFrame is a map from variable names to their types, varTypeIR. In
addition to the type of the variable, varTypeIR also contains the direction
of the variable (direction), whether it is a compile-time constant (ctk),
and whether it is associated with a compile-time known value (value?).
The compile-time known-ness is discussed in Section 7.5.
Notice that localTypingLayer contains multiple typeFrames. This is because a
local scope can be nested with block statements.
typeDefIR
: nameTypeDefIR
| aliasTypeDefIR
| dataTypeDefIR
| objectTypeDefIR
;
typeDefEnv = map<typeId, typeDefIR>
typeDefEnv is a map from type names to their type definitions, typeDefIR.
typeDefIR is a type constructor, derived from user-defined type declarations.
callableTypeDefIR
: actionTypeDefIR
| functionTypeDefIR
| methodTypeDefIR
;
callableTypeDefEnv = map<callableId, callableTypeDefIR>
Functions, methods, and actions can be defined in P416. These are
collectively called callables. callableTypeDefEnv is a map from callable
names to their type definitions, callableTypeDefIR.
constructorTypeDefIR
: CONSTRUCTOR `< typeParameterIR* , typeParameterIR* >
`( constructorParameterIR* ) : typeIR
;
constructorTypeDefEnv = map<constructorId, constructorTypeDefIR>
Stateful objects can be defined in P416, which are instantiated using
constructors. constructorTypeDefEnv is a map from constructor names to their
type definitions, constructorTypeDefIR.
7.3. Instantiation
Instantiation of a program proceeds in order of declarations, starting in the top-level namespace:
-
All declarations (e.g.,
parsers,controls, types, constants) evaluate to themselves. -
Each
tableevaluates to a table instance. -
Constructor invocations evaluate to stateful objects of the corresponding type. For this purpose, all constructor arguments are evaluated recursively and bound to the constructor parameters. Constructor arguments must be compile-time known values. The order of evaluation of the constructor arguments should be unimportant --- all evaluation orders should produce the same results.
-
Instantiations evaluate to named stateful objects.
-
The instantiation of a
parserorcontrolblock recursively evaluates all stateful instantiations declared in the block. -
The result of the program’s evaluation is the value of the top-level
mainvariable.
Note that all stateful values are instantiated at compilation time.
As an example, consider the following program fragment:
// architecture declaration
parser P(/* parameters omitted */);
control C(/* parameters omitted */);
control D(/* parameters omitted */);
package Switch(P prs, C ctrl, D dep);
extern Checksum16 { /* body omitted */}
// user code
Checksum16() ck16; // checksum unit instance
parser TopParser(/* parameters omitted */)(Checksum16 unit) { /* body omitted */}
control Pipe(/* parameters omitted */) { /* body omitted */}
control TopDeparser(/* parameters omitted */)(Checksum16 unit) { /* body omitted */}
Switch(TopParser(ck16),
Pipe(),
TopDeparser(ck16)) main;
The evaluation of this program proceeds as follows:
-
The declarations of
P,C,D,Switch, andChecksum16all evaluate to themselves. -
The
Checksum16() ck16instantiation is evaluated and it produces an object namedck16with typeChecksum16. -
The declarations for
TopParser,Pipe, andTopDeparserevaluate as themselves. -
The
mainvariable instantiation is evaluated:-
The arguments to the constructor are evaluated recursively.
-
TopParser(ck16)is a constructor invocation.-
Its argument is evaluated recursively; it evaluates to the
ck16object.
-
-
The constructor itself is evaluated, leading to the instantiation of an object of type
TopParser. -
Similarly,
Pipe()andTopDeparser(ck16)are evaluated as constructor calls. -
All the arguments of the
Switchpackage constructor have been evaluated (they are an instance ofTopParser, an instance ofPipe, and an instance ofTopDeparser). Their signatures are matched with theSwitchdeclaration. -
Finally, the
Switchconstructor can be evaluated. The result is an instance of theSwitchpackage (that contains aTopParsernamedprsthe first parameter of theSwitch; aPipenamedctrl; and aTopDeparsernameddep).
-
-
The result of the program evaluation is the value of the
mainvariable, which is the above instance of theSwitchpackage.
Figure 8 shows the result of the evaluation in a graphical form. The
result is always a graph of instances. There is only one instance of
Checksum16, called ck16, shared between the TopParser and TopDeparser.
Whether this is possible is architecture-dependent. Specific target compilers
may require distinct checksum units to be used in distinct blocks.
7.3.1. Control-plane names
Every controllable entity exposed in a P4 program must be assigned a unique, fully-qualified name, which the control plane may use to interact with that entity. The following entities are controllable.
-
value sets
-
tables
-
keys
-
actions
-
extern instances
A fully qualified name consists of the local name of a controllable entity prepended with the fully qualified name of its enclosing namespace. Hence, the following program constructs, which enclose controllable entities, must themselves have unique, fully-qualified names.
-
control instances
-
parser instances
Evaluation may create multiple instances from one type, each of which must have a unique, fully-qualified name.
7.3.1.1. Computing control-plane names
The fully-qualified name of a construct is derived by concatenating the fully-qualified name of its enclosing construct with its local name. Constructs with no enclosing namespace, i.e. those defined at the global scope, have the same local and fully-qualified names. The local names of controllable entities and enclosing constructs are derived from the syntax of a P4 program as follows.
7.3.1.1.1. Value sets
For each value_set construct, its syntactic name becomes the local name of
the value set. For example:
struct vsk_t {
@match(ternary)
bit<16> port;
}
value_set<vsk_t>(4) pvs;
This value_set’s local name is pvs.
7.3.1.1.2. Tables
For each table construct, its syntactic name becomes the local name of the
table. For example:
control c(/* parameters omitted */)() {
table t { /* body omitted */ }
}
This table’s local name is t.
7.3.1.1.3. Keys
Syntactically, table keys are expressions. For simple expressions, the local key name can be generated from the expression itself; the algorithm by which a compiler derives control-plane names for complex key expressions is target-dependent.
The spec suggests, but does not mandate, the following algorithm for generating names for some kinds of key expressions:
Kind |
Example |
Name |
The |
|
|
Array accesses. |
|
|
Constants. |
|
|
Field projections. |
|
|
Slices. |
|
|
Masks. |
|
|
In the following example, the previous algorithm would derive for table t two
keys with names data.f1 and hdrs[3].f2.
table t {
keys = {
data.f1 : exact;
hdrs[3].f2 : exact;
}
actions = { /* body omitted */ }
}
If a compiler cannot generate a name for a key it requires the key expression
to be annotated with a @name annotation
(Section 20.2.3), as in the following example:
table t {
keys = {
data.f1 + 1 : exact @name("f1_mask");
}
actions = { /* body omitted */ }
}
Here, the @name("f1_mask") annotation assigns the local name "f1_mask" to
this key.
7.3.1.1.4. Actions
For each action construct, its syntactic name is the local name of the
action. For example:
control c(/* parameters omitted */)() {
action a(...) { /* body omitted */ }
}
This action’s local name is a.
7.3.1.1.5. Instances
The local names of extern, parser, and control instances are derived
based on how the instance is used. If the instance is bound to a name, that
name becomes its local control plane name. For example, if control C is
declared as,
control C(/* parameters omitted */)() { /* body omitted */ }
and instantiated as,
C() c_inst;
then the local name of the instance is c_inst.
Alternatively, if the instance is created as an actual argument, then its local
name is the name of the formal parameter to which it will be bound. For
example, if extern E and control C are declared as,
extern E { /* body omitted */ }
control C(/* parameters omitted */)(E e_in) { /* body omitted */ }
and instantiated as,
C(E()) c_inst;
then the local name of the extern instance is e_in.
If the construct being instantiated is passed as an argument to a package, the
instance name is derived from the user-supplied type definition when possible.
In the following example, the local name of the instance of MyC is c, and
the local name of the extern is e2, not e1.
extern E { /* body omitted */ }
control ArchC(E e1);
package Arch(ArchC c);
control MyC(E e2)() { /* body omitted */ }
Arch(MyC()) main;
Note that in this example, the architecture will supply an instance of the
extern when it applies the instance of MyC passed to the Arch package. The
fully-qualified name of that instance is main.c.e2.
Next, consider a larger example that demonstrates name generation when there are multiple instances.
control Callee() {
table t { /* body omitted */ }
apply { t.apply(); }
}
control Caller() {
Callee() c1;
Callee() c2;
apply {
c1.apply();
c2.apply();
}
}
control Simple();
package Top(Simple s);
Top(Caller()) main;
The compile-time evaluation of this program produces the structure in
Figure 9. Notice that there are two instances of the table t.
These instances must both be exposed to the control plane. To name an object in
this hierarchy, one uses a path composed of the names of containing instances.
In this case, the two tables have names s.c1.t and s.c2.t, where s is the
name of the argument to the package instantiation, which is derived from the
name of its corresponding formal parameter.
7.3.1.2. Annotations controlling naming
Control plane-related annotations (Section 20.2.3) can alter the names exposed to the control plane in the following ways.
-
The
@hiddenannotation hides a controllable entity from the control plane. This is the only case in which a controllable entity is not required to have a unique, fully-qualified name. -
The
@nameannotation may be used to change the local name of a controllable entity.
Programs that yield the same fully-qualified name for two different controllable entities are invalid.
7.3.1.3. Recommendations
The control plane may refer to a controllable entity by a postfix of its fully qualified name when it is unambiguous in the context in which it is used. Consider the following example.
control c(/* parameters omitted */)() {
action a (/* parameters omitted */) { /* body omitted */ }
table t {
keys = { /* body omitted */ }
actions = { a; } }
}
c() c_inst;
Control plane software may refer to action c_inst.a as a when inserting
rules into table c_inst.t, because it is clear from the definition of the
table which action a refers to.
Not all unambiguous postfix shortcuts are recommended. For instance, consider
the last example in Section 7.3.1.1.5. One might be tempted to
refer to s.c1 simply as c1, as no other instance named c1 appears in the
program. However, this leads to a brittle program since future modifications
can never introduce an instance named c1, or include libraries of P4 code
that contain instances with that name.
7.3.2. Instantiating a P4 program
objectId = nameIR*
object
: externObject
| parserObject
| controlObject
| packageObject
| tableObject
| valueSetObject
;
The stateful elements of a P4 program are represented as objects. These
objects are identified by their fully-qualified names (objectId).
store = map<objectId, object>
A P416 program is first type checked to yield a P4IR program, and is then
instantiated to create a store, which maps objectIds to objects.
Click to view the specification source
relation Program_inst: |- p4program : globalInstLayer store
Click to view the specification source
rulegroup Program_inst: rule Program_inst: |- p4program : IC_1.GLOBAL STO_1 -- Program_ok: |- p4program : (declarationIR* ;) -- if IC_0 = $empty_instContext -- if STO_0 = $empty_store -- Decls_inst: IC_0 STO_0 |- declarationIR* : IC_1 STO_1
7.3.3. Instantiation context
globalInstLayer = {
CONSTRUCTOR constructorDefEnv,
TYPE typeDefEnv,
CALLABLE callableDefEnv,
FRAME frame
}
blockInstLayer = {
TYPE theta,
CALLABLE callableDefEnv,
STATE stateEnv,
FRAME frame
}
localInstLayer = {
TYPE theta,
FRAMES frame*
}
instContext = {
PATH objectId,
GLOBAL globalInstLayer,
BLOCK blockInstLayer,
LOCAL localInstLayer
}
An instantiation context is used to keep track of the types and compile-time known values of all named declarations in scope at a given program point.
-
A global instantiation layer, which contains all declarations at the top level of a P4 program.
-
A block instantiation layer, which contains all declarations in the current block scope.
-
A local instantiation layer, which contains all declarations in the current local scope.
value
: baseValue
| dataValue
| objectReferenceValue
| synthesizedValue
;
frame = map<id, value>
frame is a map from variable names to their compile-time known values,
value. The compile-time known-ness is discussed in
Section 7.5.
typeDefIR
: nameTypeDefIR
| aliasTypeDefIR
| dataTypeDefIR
| objectTypeDefIR
;
typeDefEnv = map<typeId, typeDefIR>
typeDefEnv is a map from type names to their type definitions, typeDefIR.
typeDefIR is a type constructor, derived from user-defined type declarations.
callableDef
: actionDef
| functionDef
| methodDef
;
callableDefEnv = map<callableId, callableDef>
callableDefEnv is a map from callable names to their definitions,
callableDef.
constructorDef
: externObjectConstructorDef
| parserObjectConstructorDef
| controlObjectConstructorDef
| packageObjectConstructorDef
;
constructorDefEnv = map<callableId, constructorDef>
Stateful objects are instantiated using constructors. constructorDefEnv is a
map from constructor names to their definitions, constructorDef.
7.4. Runtime evaluation
The runtime evaluation of a P4 program is orchestrated by the architecture
model. Each architecture model needs to specify the order and the conditions
under which the various P4 component programs are dynamically executed. For
example, in the Simple Switch example from Section 5.1 the execution flow
goes Parser → Pipe → Deparser.
Once a P4 execution block is invoked its execution proceeds until termination according to the semantics defined in this document. P4 execution blocks are represented as objects in the global store, which is statically allocated during instantiation.
7.4.1. Evaluating a P4 program
P4 program evaluation proceeds by invoking the objects in the global store. A call to a programmable block invokes the corresponding abstract machine:
Click to view the specification source
relation Call_eval: cursor evalContext arch |- callee @ `<typeArgumentListIR> `(argumentListIR) : evalContext arch callResult
A call to an extern function invokes the corresponding extern function:
Click to view the specification source
extern relation ExternFunctionCall_eval: evalContext arch |- nameIR `(nameIR*) : evalContext arch callResult
A call to an extern method invokes the corresponding extern method:
Click to view the specification source
extern relation ExternMethodCall_eval: evalContext arch |- objectId . nameIR `(nameIR*) : evalContext arch callResult
7.4.2. Evaluation context
globalEvalLayer = globalInstLayer
blockEvalLayer = blockInstLayer
localEvalLayer = localInstLayer
evalContext = {
GLOBAL globalEvalLayer,
BLOCK blockEvalLayer,
LOCAL localEvalLayer
}
An evaluation context is used to keep track of the types and compile-time known values of all named declarations in scope at a given program point.
-
A global evaluation layer, which contains all declarations at the top level of a P4 program.
-
A block evaluation layer, which contains all declarations in the current block scope.
-
A local evaluation layer, which contains all declarations in the current local scope.
The layers are the same as in the instantiation context, except that the
frame is a map from variable names to their current runtime values.
The following sections describe the abstract machines that define the dynamic semantics of the programmable blocks in a P4 target.
7.4.3. The parser abstract machine
A parser starts execution in the start state and ends execution when one of
the reject or accept states has been reached.
An architecture must specify the behavior when the accept and reject states
are reached. For example, an architecture may specify that all packets reaching
the reject state are dropped without further processing. Alternatively, it
may specify that such packets are passed to the next block after the parser,
with intrinsic metadata indicating that the parser reached the reject state,
along with the error recorded.
7.4.4. The match-action pipeline abstract machine
We can describe the computational model of a match-action pipeline, embodied by a control block: the body of the control block is executed, similarly to the execution of a traditional imperative program:
-
At runtime, statements within a block are executed in the order they appear in the control block.
-
Execution of the
returnstatement causes the immediate termination of the execution of the currentcontrolblock and a return to the caller. -
Execution of the
exitstatement causes the immediate termination of the execution of the currentcontrolblock and of all the enclosing callercontrolblocks. -
Applying a
tableexecutes the corresponding match-action unit.
7.4.5. Concurrency model
A typical packet processing system needs to execute multiple simultaneous
logical "threads." At the very least there is a thread executing the control
plane, which can modify the contents of the tables. Architecture specifications
should describe in detail the interactions between the control-plane and the
data-plane. The data plane can exchange information with the control plane
through extern function and method calls. Moreover, high-throughput
packet-processing systems may be processing multiple packets simultaneously,
e.g., in a pipelined fashion, or concurrently parsing a first packet while
performing match-action operations on a second packet. This section specifies
the semantics of P4 programs with respect to such concurrent executions.
Each top-level parser or control block is executed as a separate thread
when invoked by the architecture. All the parameters of the block and all local
variables are thread-local — i.e., each thread has a private copy of these
resources. This applies to the packet_in and packet_out parameters of
parsers and deparsers.
As long as a P4 block uses only thread-local storage (e.g., metadata, packet headers, local variables), its behavior in the presence of concurrency is identical with the behavior in isolation, since any interleaving of statements from different threads must produce the same output.
In contrast, extern blocks instantiated by a P4 program are global, shared
across all threads. If extern blocks mediate access to state (e.g., counters,
registers) — i.e., the methods of the extern block read and write state,
these stateful operations are subject to data races. P4 mandates that execution
of a method call on an extern instance is atomic.
To allow users to express atomic execution of larger code blocks, P4 provides
an @atomic annotation, which can be applied to block statements, parser
states, control blocks, or whole parsers.
Consider the following example:
extern Register { /* body omitted */ }
control Ingress() {
Register() r;
table flowlet { /* read state of r in an action */ }
table new_flowlet { /* write state of r in an action */ }
apply {
@atomic {
flowlet.apply();
if (ingress_metadata.flow_ipg > FLOWLET_INACTIVE_TIMEOUT)
new_flowlet.apply();
}}}
This program accesses an extern object r of type Register in actions
invoked from tables flowlet (reading) and new_flowlet (writing). Without
the @atomic annotation these two operations would not execute atomically: a
second packet may read the state of r before the first packet had a chance to
update it.
Note that even within an action definition, if the action does something like
reading a register, modifying it, and writing it back, in a way that only the
modified value should be visible to the next packet, then, to guarantee correct
execution in all cases, that portion of the action definition should be
enclosed within a block annotated with @atomic.
A compiler backend must reject a program containing @atomic blocks if it
cannot implement the atomic execution of the instruction sequence. In such
cases, the compiler should provide reasonable diagnostics.
The P4 semantics described throughout this document assumes a sequential execution model. Thus, it does not describe the interactions between multiple threads accessing shared state. The purpose of this document is to clearly define the semantics of P4 constructs in isolation. The exact concurrency model, including the interactions between multiple threads, is target-dependent and beyond the scope of this document.
7.5. Compile-time known and local compile-time known values
Certain expressions in a P4 program have the property that their value can be determined at compile time. Moreover, for some of these expressions, their value can be determined only using information in the current scope. We call these compile-time known values and local compile-time known values respectively.
The following are local compile-time known values:
-
Integer literals, Boolean literals, and string literals.
-
Identifiers declared as constants using the
constkeyword. -
Identifiers declared in an
error,enum, ormatch_kinddeclaration. -
The
defaultidentifier. -
The
sizefield of a value with type header stack. -
The
_identifier when used as aselectexpression label. -
The expression
{#}representing an invalid header or header union value. -
Identifiers that represent declared types, actions, functions, tables, parsers, controls, or packages.
-
Tuple expression where all components are local compile-time known values.
-
Structure-valued expressions, where all fields are local compile-time known values.
-
Expressions evaluating to a list type, where all elements are local compile-time known values.
-
Legal casts applied to local compile-time known values.
-
Indexing a local compile-time known stack or tuple value with a local compile-time known index.
-
Accessing a field of a local compile-time known struct, header, or header union value.
-
The following expressions (
+,-,|+|,|-|,*,/,%,!,&,|,^,&&,||,<<,>>,~,>,<,==,!=,<=,>=,++,[:],?:) when their operands are all local compile-time known values. -
Expressions of the form
e.minSizeInBits(),e.minSizeInBytes(),e.maxSizeInBits()ande.maxSizeInBytes()where the type ofeis not generic.
The following are compile-time known values:
-
All local compile-time known values.
-
Constructor parameters (i.e., the declared parameters for a
parser,control, etc.) -
Tuple expression where all components are compile-time known values.
-
Structure-valued expressions, where all fields are compile-time known values.
-
Expressions evaluating to a list type, where all elements are compile-time known values.
-
Legal casts applied to compile-time known values.
-
Indexing a compile-time known stack or tuple value with a compile-time known index.
-
Accessing a field of a compile-time known struct, header, or header union value.
-
The following expressions (
+,-,|+|,|-|,*,/,%,!,&,|,^,&&,||,<<,>>,~,>,<,==,!=,<=,>=,++,[:],?:) when their operands are all compile-time known values. -
Expressions of the form
e.minSizeInBits(),e.minSizeInBytes(),e.maxSizeInBits()ande.maxSizeInBytes()where the the type ofeis generic.
Intuitively, the main difference between compile-time known values and local
compile-time known values is that the former also contains constructor
parameters. The distinction is important when it comes to defining the meaning
of features like types. For example, in the type bit<e>, the expression e
must be a local compile-time known value. Suppose instead that e were a
constructor parameter—i.e., merely a compile-time known value. In this
situation, it would be impossible to resolve bit<e> to a concrete type using
purely local information—we would have to wait until the constructor was
instantiated and the value of e known.
Local compile-time known values can be identified in the type checking phase. Compile-time known values can be identified after type checking, during the instantiation phase. Once the constructor parameters are bound to actual arguments and type parameters are bound to actual types, compile-time known values can be evaluated to concrete values.
ctk is a marker used in P4IR to indicate whether an expression is local
compile-time known, compile-time known, or neither.
ctk
: LCTK
| CTK
| DYN
;
typedExpressionIRs in P4IR, produced by typing expressions in P416,
annotated with their type typeIR and a ctk marker indicating the
compile-time known status.
expressionNoteIR
: `( typeIR ctk )
;
typedExpressionIR
: expressionIR # expressionNoteIR
;
8. P4 types and values
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.
The syntax for P4 types is as follows:
type
: baseType
| namedType
| headerStackType
| listType
| tupleType
;
typeOrVoid
: type
| VOID
| identifier
;
These types are represented in P4IR as follows:
typeIR
: baseTypeIR
| nameTypeIR
| aliasTypeIR
| dataTypeIR
| objectTypeIR
| synthesizedTypeIR
;
The types represent the kinds of values that can be used in P4 programs. The runtime representation of P4 values is as follows:
value
: baseValue
| dataValue
| objectReferenceValue
| synthesizedValue
;
8.1. Semantics of types
8.1.1. Type checking surface types
typeIR is constructed from type checking the surface syntax of P4 types
(type) with the following relation:
8.1.2. Well-formedness
The P4 type system also imposes well-formedness constraints on types, which enforce additional restrictions on the structure of types, such as type nesting.
8.2. Base types and values
P4 supports the following built-in base types:
-
bool, which represents Boolean values -
The
errortype, which is used to convey errors in a target-independent, compiler-managed way. -
The
match_kindtype, which is used for describing the implementation of table lookups, -
The
stringtype, which can be used with compile-time known values of type string. -
int, which represents arbitrary-sized integer values -
Fixed-width signed integers represented using two’s complement
int<> -
Bit-strings of fixed width, denoted by
bit<> -
Bit-strings of dynamically-computed width with a fixed maximum width
varbit<>
baseType
: BOOL
| ERROR
| MATCH_KIND
| STRING
| INT
| INT `< int >
| INT `< `( expression ) >
| BIT
| BIT `< int >
| BIT `< `( expression ) >
| VARBIT `< int >
| VARBIT `< `( expression ) >
;
In addition, P4 supports void types:
typeOrVoid
: type
| VOID
| identifier
;
-
The
voidtype, which has no values and can be used only in a few restricted circumstances.
In P4IR, base types and values are represented as:
baseTypeIR
: voidTypeIR
| boolTypeIR
| errorTypeIR
| matchKindTypeIR
| stringTypeIR
| integerTypeIR
;
baseValue
: boolValue
| errorValue
| matchKindValue
| stringValue
| integerValue
;
8.2.1. Well-formedness
The well-formedness rules for base types are as follows:
Click to view the specification source
rulegroup Type_wf/baseTypeIR: rule Type_wf/baseTypeIR: B |- baseTypeIR
8.2.2. The void type
voidTypeIR
: VOID
;
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.
There is no value of type void.
8.2.2.1. Type checking
Click to view the specification source
rulegroup Type_ok/void: rule Type_ok/void: p TC |- VOID : VOID # eps
8.2.3. Booleans
boolTypeIR
: BOOL
;
boolValue
: `B bool
;
The Boolean type bool contains just two values, false and true. Boolean
values are not integers or bit-strings.
8.2.3.1. Operations
The following operations are provided on Boolean expressions:
-
And, denoted by
&& -
Or, denoted by
|| -
Negation, denoted by
! -
Equality and inequality tests, denoted by
==and!=respectively.
The precedence of these operators is similar to C and uses short-circuited evaluation where relevant.
Additionally, the size of a boolean can be determined at compile-time ([sec-minsizeinbits]).
P4 does not implicitly cast from bit-strings to Booleans or vice versa. As a consequence, a program that is valid in a language like C such as,
if (x) /* body omitted */
(where x has an integer type) must instead be written in P4 as:
if (x != 0) /* body omitted */
See the discussion on arbitrary-precision types and implicit casts in
Section 17.2 for details on how the 0 in this expression is
evaluated.
8.2.3.2. Type checking
Click to view the specification source
rulegroup Type_ok/boolean: rule Type_ok/boolean: p TC |- BOOL : BOOL # eps
8.2.4. Errors
errorTypeIR
: ERROR
;
errorValue
: ERROR . nameIR
;
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 following syntax:
errorDeclaration
: ERROR `{ nameList }
;
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.
Error declaration is described in detail in Section 11.7.
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.
See Section 11.7 for the semantics of error declarations and Section 14.12 for accessing error values.
8.2.4.1. Operations
Symbolic names declared by an error declaration belong to the error
namespace. The error type only supports equality (==) and inequality
(!=) comparisons. The result of such a comparison is a Boolean value.
For example, the following operation tests for the occurrence of an error:
error errorFromParser;
if (errorFromParser != error.NoError) { /* code omitted */ }
8.2.4.2. Type checking
Click to view the specification source
rulegroup Type_ok/error: rule Type_ok/error: p TC |- ERROR : ERROR # eps
8.2.5. Match kinds
matchKindTypeIR
: MATCH_KIND
;
matchKindValue
: MATCH_KIND . nameIR
;
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 [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.
matchKindDeclaration
: MATCH_KIND `{ nameList trailingCommaOpt }
;
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.
Match kind declaration is described in detail in Section 11.8.
8.2.5.1. Operations
Values of type match_kind are similar to enum values. They support only
assignment and comparisons for equality and inequality.
match_kind { fuzzy }
const bool same = exact == fuzzy; // always 'false'
8.2.5.2. Type checking
Click to view the specification source
rulegroup Type_ok/matchKind: rule Type_ok/matchKind: p TC |- MATCH_KIND : MATCH_KIND # eps
8.2.6. Strings
stringTypeIR
: STRING
;
stringValue = stringLiteral
The type string represents strings. The values of type string are either
string literals, or concatenations of multiple string-typed expressions.
One cannot declare variables with a string type. Parameters with type
string can be only directionless (see Section Section 18.4).
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 Section 6.2.2.3). 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 */ }
8.2.6.1. Operations
The only operation allowed on strings is concatenation, denoted by ++. For
string concatenation, both operands must be strings and the result is also a
string. String concatenation can only be performed at compile time.
extern void log(string message);
void foo(int<8> v) {
// ...
log("my log message " ++
"continuation of the log message");
}
8.2.6.2. Type checking
Click to view the specification source
rulegroup Type_ok/string: rule Type_ok/string: p TC |- STRING : STRING # eps
8.2.7. Integers (signed and unsigned)
integerTypeIR
: intTypeIR
| fixedIntTypeIR
| fixedBitTypeIR
| varBitTypeIR
;
integerLiteral
: D int
| nat W int
| nat S int
;
integerValue
: integerLiteral
| nat . nat V int
;
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.
8.2.7.1. 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.
8.2.7.2. Integer literals
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 6.2.2.
| Literal | Interpretation |
|---|---|
|
Type is |
|
Type is |
|
Type is |
|
Type is |
|
Type is |
|
Type is |
8.2.7.3. Illegal arithmetic expressions
Many arithmetic expressions that would be allowed in other languages are illegal in P4. To illustrate, consider the following declarations:
bit<8> x;
bit<16> y;
int<8> z;
The table below shows several expressions which are illegal because they do not obey the P4 typing rules. For each expression we provide several ways that the expression could be manually rewritten into a legal expression. Note that for some expression there are several legal alternatives, which may produce different results! The compiler cannot guess the user intent, so P4 requires the user to disambiguate.
| Expression | Why it is illegal | Alternatives |
|---|---|---|
|
Different widths |
|
|
||
|
Different signedness |
|
|
||
|
Cannot change both sign and width |
|
|
||
|
Different widths and signs |
|
|
||
|
||
|
||
|
RHS of shift cannot be signed |
|
|
Different signs |
|
|
||
|
Either LHS should have a fixed width (bit shift), |
|
Or RHS must be compile-time known (int shift) |
None |
|
|
Bitwise operation on int |
|
|
Bitwise operation on int |
|
8.2.7.4. 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.
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 18.4 for more details on directionless parameters.
8.2.7.4.1. Operations
The type int denotes arbitrary-precision integers. In P4, all expressions of
type int must be compile-time known values. The type int supports the
following operations:
-
Negation, denoted by unary
- -
Unary plus, denoted by
+. This operation behaves like a no-op. -
Addition, denoted by
+. -
Subtraction, denoted by
-. -
Comparison for equality and inequality, denoted by
==and!=respectively. These operations produce a Boolean result. -
Numeric comparisons
<,<=,>, and>=. These operations produce a Boolean result. -
Multiplication, denoted by
*. -
Truncating integer division between positive values, denoted by
/. -
Modulo between positive values, denoted by
%. -
Arithmetic shift left and right denoted by
<<and>>. These operations produce anintresult. The right operand must be either an unsigned value of typebit<S>or a compile-time known value that is a non-negative integer. -
Bit slices, denoted by
[H:L], whereHandLmust be expressions that evaluate to non-negative, local compile-time known values, andH >= Lmust be true. The types ofHandL(which do not need to be identical) must be one of the following:-
int- an arbitrary-precision integer ([sec-arbitrary-precision-integers]) -
bit<W>- aW-bit unsigned integer whereW >= 0([sec-unsigned-integers]) -
int<W>- aW-bit signed integer whereW >= 1([sec-signed-integers]) -
a serializable
enumwith an underlying type that isbit<W>orint<W>([sec-enum-types]).
The result is an unsigned bit-string of width
H - L + 1, including the bits numbered fromL(which becomes the least significant bit of the result) toH(the most significant bit of the result) from the source operand. The conditions0 <= L <= Hare checked statically. If necessary, the source integer value that is sliced is automatically extended to have a width withHbits. Note that both endpoints of the extraction are inclusive. The bounds are required to be values that are known at compile time so that the width of the result can be computed at compile time. A slice of a negative or positive value is always a positive value. -
Each operand that participates in any of these operation must have type int
(except shifts). Binary operations cannot be used to combine values of type
int with values of a fixed-width type (except shifts). However, the compiler
automatically inserts casts from int to fixed-width types in certain
situations—see Chapter 17.
All computations on int values are carried out without loss of information.
For example, multiplying two 1024-bit values may produce a 2048-bit value (note
that concrete representation of int values is not specified). int values
can be cast to bit<w> and int<w> values. Casting an int value to a
fixed-width type will preserve the least-significant bits. If truncation causes
significant bits to be lost, the compiler should emit a warning.
Note: bitwise-operations (|, &, ^, ~) are not defined on expressions of
type int. In addition, it is illegal to apply division and modulo to negative
values. Saturating arithmetic is not supported for arbitrary-precision
integers.
8.2.7.4.2. Type checking
Click to view the specification source
rulegroup Type_ok/arbitraryInt: rule Type_ok/arbitraryInt: p TC |- INT : INT # eps
8.2.7.5. Fixed-width 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 7.5) 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 local 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).
8.2.7.5.1. Operations
This section discusses all operations that can be performed on expressions of
type bit<W> for some width W, also known as bit-strings.
Arithmetic operations "wrap around", similar to C operations on unsigned values (i.e., representing a large value on W bits will only keep the least-significant W bits of the value). In particular, P4 does not have arithmetic exceptions—the result of an arithmetic operation is defined for all possible inputs.
P4 target architectures may optionally support saturating arithmetic. All
saturating operations are limited to a fixed range between a minimum and
maximum value. Saturating arithmetic has advantages, in particular when used as
counters. The result of a saturating counter max-ing out is much closer to the
real result than a counter that overflows and wraps around. According to
Wikipedia Saturating
Arithmetic is as numerically close to the true answer as possible; for 8-bit
binary signed arithmetic, when the correct answer is 130, it is considerably
less surprising to get an answer of 127 from saturating arithmetic than to get
an answer of −126 from modular arithmetic. Likewise, for 8-bit binary unsigned
arithmetic, when the correct answer is 258, it is less surprising to get an
answer of 255 from saturating arithmetic than to get an answer of 2 from
modular arithmetic. At this time, P4 defines saturating operations only for
addition and subtraction. For an unsigned integer with bit-width of W, the
minimum value is 0 and the maximum value is 2^W-1. The precedence of
saturating addition and subtraction operations is the same as for modular
arithmetic addition and subtraction.
All binary operations except shifts and concatenation require both operands to have the same exact type and width; supplying operands with different widths produces an error at compile time. No implicit casts are inserted by the compiler to equalize the widths. There are no other binary operations that accept signed and unsigned values simultaneously besides shifts and concatenation. The following operations are provided on bit-string expressions:
-
Test for equality between bit-strings of the same width, designated by
==. The result is a Boolean value. -
Test for inequality between bit-strings of the same width, designated by
!=. The result is a Boolean value. -
Unsigned comparisons
<,>,<=,>=. Both operands must have the same width and the result is a Boolean value.
Each of the following operations produces a bit-string result when applied to bit-strings of the same width:
-
Negation, denoted by unary
-. The result is computed by subtracting the value from 2W. The result is unsigned and has the same width as the input. The semantics is the same as the C negation of unsigned numbers. -
Unary plus, denoted by
+. This operation behaves like a no-op. -
Addition, denoted by
+. This operation is associative. The result is computed by truncating the result of the addition to the width of the output (similar to C). -
Subtraction, denoted by
-. The result is unsigned, and has the same type as the operands. It is computed by adding the negation of the second operand (similar to C). -
Multiplication, denoted by
*. The result has the same width as the operands and is computed by truncating the result to the output’s width. P4 architectures may impose additional restrictions—e.g., they may only allow multiplication by a non-negative integer power of two. -
Bitwise "and" between two bit-strings of the same width, denoted by
&. -
Bitwise "or" between two bit-strings of the same width, denoted by
|. -
Bitwise "complement" of a single bit-string, denoted by
~. -
Bitwise "xor" of two bit-strings of the same width, denoted by
^. -
Saturating addition, denoted by
|+|. -
Saturating subtraction, denoted by
|-|.
Bit-strings also support the following operations:
-
Logical shift left and right by a non-negative integer value (which need not be a compile-time known value), denoted by
<<and>>respectively. In a shift, the left operand is unsigned, and right operand must be either an expression of typebit<S>or a non-negative integer value that is known at compile time. The result has the same type as the left operand. Shifting by an amount greater than or equal to the width of the input produces a result where all bits are zero. -
Extraction of a set of contiguous bits, also known as a slice, denoted by
[H:L], whereHandLmust be expressions that evaluate to non-negative, local compile-time known values, andH >= L. The types ofHandL(which do not need to be identical) must be numeric ([sec-numeric-values]). The result is a bit-string of widthH - L + 1, including the bits numbered fromL(which becomes the least significant bit of the result) toH(the most significant bit of the result) from the source operand. The conditions0 <= L <= H < Ware checked statically (whereWis the length of the source bit-string). Note that both endpoints of the extraction are inclusive. The bounds are required to be local compile-time known values so that the width of the result can be computed at compile time. Slices are also l-values, which means that P4 supports assigning to a slice:e[H:L] = x. The effect of this statement is to set bitsHthroughL(inclusive of both) ofeto the bit-pattern represented byx, and leaves all other bits ofeunchanged. A slice of an unsigned integer is an unsigned integer. -
Concatenation of bit-strings and/or fixed-width signed integers, denoted by
++. The two operands must be eitherbit<W>orint<W>, and they can be of different signedness and width. The result has the same signedness as the left operand and the width equal to the sum of the two operands' width. In concatenation, the left operand is placed as the most significant bits.
Additionally, the size of a bit-string can be determined at compile-time ([sec-minsizeinbits]).
8.2.7.5.2. Type checking
Click to view the specification source
rulegroup Type_ok/fixedBit: rule Type_ok/none: p TC |- BIT : (BIT `<1>) # eps rule Type_ok/integer: p TC |- BIT `<n> : (BIT `<n>) # eps rule Type_ok/expression: p TC |- BIT `<`(expression)> : (BIT `<n>) # eps -- Expr_ok: p TC |- expression : typedExpressionIR -- if LCTK = $ctk_of_typedExpressionIR(typedExpressionIR) -- Expr_eval_lctk: p TC |- typedExpressionIR ~> integerValue -- if n = $nat_of_integerValue(integerValue)
8.2.7.6. Fixed-width 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 Section 7.5) 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).
A signed integer with width 1 can only have two legal values: 0 and -1.
8.2.7.6.1. Operations
This section discusses all operations that can be performed on expressions of
type int<W> for some W. Recall that the int<W> denotes signed W-bit
integers, represented using two’s complement.
In general, P4 arithmetic operations do not detect "underflow" or "overflow":
operations simply "wrap around", similar to C operations on unsigned values.
Hence, attempting to represent large values using W bits will only keep the
least-significant W bits of the value.
P4 supports saturating arithmetic (addition and subtraction) for signed
integers. Targets may optionally reject programs using saturating arithmetic.
For a signed integer with bit-width of W, the minimum value is -2^(W-1) and
the maximum value is 2^(W-1)-1.
P4 also does not support arithmetic exceptions. The runtime result of an arithmetic operation is defined for all combinations of input arguments.
All binary operations except shifts and concatenation require both operands to have the same exact type (signedness) and width and supplying operands with different widths or signedness produces a compile-time error. No implicit casts are inserted by the compiler to equalize the types. Except for shifts and concatenation, P4 does not have any binary operations that operate simultaneously on signed and unsigned values.
Note that bitwise operations on signed integers are well-defined, since the representation is mandated to be two’s complement.
The int<W> datatype supports the following operations; all binary operations
require both operands to have the exact same type. The result always has the
same width as the left operand.
-
Negation, denoted by unary
-. -
Unary plus, denoted by
+. This operation behaves like a no-op. -
Addition, denoted by
+. -
Subtraction, denoted by
-. -
Comparison for equality and inequality, denoted
==and!=respectively. These operations produce a Boolean result. -
Numeric comparisons, denoted by
<,<=,>,and>=. These operations produce a Boolean result. -
Multiplication, denoted by
*. Result has the same width as the operands. P4 architectures may impose additional restrictions—e.g., they may only allow multiplication by a power of two. -
Bitwise "and" between two bit-strings of the same width, denoted by
&. -
Bitwise "or" between two bit-strings of the same width, denoted by
|. -
Bitwise "complement" of a single bit-string, denoted by
~. -
Bitwise "xor" of two bit-strings of the same width, denoted by
^. -
Saturating addition, denoted by
|+|. -
Saturating subtraction, denoted by
|-|.
The int<W> datatype also support the following operations:
-
Arithmetic shift left and right denoted by
<<and>>. The left operand is signed and the right operand must be either an unsigned number of typebit<S>or a compile-time known value that is a non-negative integer. The result has the same type as the left operand. Shifting left produces the exact same bit pattern as a shift left of an unsigned value. Shift left can thus overflow, when it leads to a change of the sign bit. Shifting by an amount greater than the width of the input produces a "correct" result:-
all result bits are zero when shifting left
-
all result bits are zero when shifting a non-negative value right
-
all result bits are one when shifting a negative value right
-
-
Extraction of a set of contiguous bits, also known as a slice, denoted by
[H:L], whereHandLmust be expressions that evaluate to non-negative, local compile-time known values, andH >= Lmust be true. The types ofHandL(which do not need to be identical) must be numeric ([sec-numeric-values]). The result is an unsigned bit-string of widthH - L + 1, including the bits numbered fromL(which becomes the least significant bit of the result) toH(the most significant bit of the result) from the source operand. The conditions0 <= L <= H < Ware checked statically (whereWis the length of the source bit-string). Note that both endpoints of the extraction are inclusive. The bounds are required to be values that are known at compile time so that the width of the result can be computed at compile time. Slices are also l-values, which means that P4 supports assigning to a slice:e[H:L] = x. The effect of this statement is to set bitsHthroughLofeto the bit-pattern represented byx, and leaves all other bits ofeunchanged. A slice of a signed integer is treated as an unsigned integer. -
Concatenation of bit-strings and/or fixed-width signed integers, denoted by
++. The two operands must be eitherbit<W>orint<W>, and they can be of different signedness and width. The result has the same signedness as the left operand and the width equal to the sum of the two operands' width. In concatenation, the left operand is placed as the most significant bits.
Additionally, the size of a fixed-width signed integer can be determined at compile-time ([sec-minsizeinbits]).
8.2.7.6.2. Type checking
Click to view the specification source
rulegroup Type_ok/fixedInt: rule Type_ok/integer: p TC |- INT `<n> : (INT `<n>) # eps rule Type_ok/expression: p TC |- INT `<`(expression)> : (INT `<n>) # eps -- Expr_ok: p TC |- expression : typedExpressionIR -- if LCTK = $ctk_of_typedExpressionIR(typedExpressionIR) -- Expr_eval_lctk: p TC |- typedExpressionIR ~> integerValue -- if n = $nat_of_integerValue(integerValue)
8.2.7.7. 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
Section 7.5) 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 local 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.
8.2.7.7.1. Operations
To support parsing headers with variable-length fields, P4 offers a type
varbit. Each occurrence of the type varbit has a statically-declared
maximum width, as well as a dynamic width, which must not exceed the static
bound. Prior to initialization a variable-size bit-string has an unknown
dynamic width.
Variable-length bit-strings support a limited set of operations:
-
Assignment to another variable-sized bit-string. The target of the assignment must have the same static width as the source. When executed, the assignment sets the dynamic width of the target to the dynamic width of the source.
-
Comparison for equality or inequality with another
varbitfield. Twovarbitfields can be compared only if they have the same type. Two varbits are equal if they have the same dynamic width and all the bits up to the dynamic width are the same.
The following operations are not supported directly on a value of type
varbit, but instead on any type for which extract and emit operations are
supported (e.g. a value with type header) that may contain a field of type
varbit. They are mentioned here only to ease finding this information in a
section dedicated to type varbit.
-
Parser extraction into a header containing a variable-sized bit-string using the two-argument
extractmethod of apacket_inextern object (see [sec-packet-extract-two]). This operation sets the dynamic width of the field. -
The
emitmethod of apacket_outextern object can be performed on a header and a few other types (see [sec-deparse]) that contain a field with typevarbit. Such anemitmethod call inserts a variable-sized bit-string with a known dynamic width into the packet being constructed.
Additionally, the maximum size of a variable-length bit-string can be determined at compile-time ([sec-minsizeinbits]).
8.2.7.7.2. Type checking
Click to view the specification source
rulegroup Type_ok/variableBit: rule Type_ok/integer: p TC |- VARBIT `<n> : (VARBIT `<n>) # eps rule Type_ok/expression: p TC |- VARBIT `<`(expression)> : (VARBIT `<n>) # eps -- Expr_ok: p TC |- expression : typedExpressionIR -- if LCTK = $ctk_of_typedExpressionIR(typedExpressionIR) -- Expr_eval_lctk: p TC |- typedExpressionIR ~> integerValue -- if n = $nat_of_integerValue(integerValue)
8.3. Named types
prefixedTypeName
: typeName
| `TID . typeName
;
specializedType
: prefixedTypeName `< typeArgumentList >
;
namedType
: prefixedTypeName
| specializedType
;
Named types are types that reference another type by name. User-defined types
can be introduced with their names. For example, the following declares a new
struct type S:
struct S {
bit<32> x;
bit<32> y;
}
This type can then be referenced by its name, as can be seen in the parameter type of the following example:
void f(S s) {}
Here, S is a named type (typeName) that refers to the struct type defined earlier.
Types such as struct, header, and header union types can be generic. In order to use such a generic type it must be specialized with appropriate type arguments. For example,
// generic structure type
struct S<T> {
T field;
bool valid;
}
struct G<T> {
S<T> s;
}
// 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<T> {
T field;
}
// 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<T> {
H<bit<32>> h32;
H<bit<8>> h8;
H<T> ht;
}
// Header union with a type obtained by specializing a generic header union type
HU<bit> hu;
To handle named types used as return types, the following production is also used:
identifier
: `ID text
;
typeOrVoid
: type
| VOID
| identifier
;
8.3.1. Type definitions (type constructors)
typeDeclaration
: derivedTypeDeclaration
| typedefDeclaration
| parserTypeDeclaration
| controlTypeDeclaration
| packageTypeDeclaration
;
typeDeclaration introduces a user-defined type definition. These can be
referenced by name. Generic type definitions can be specialized with type
arguments to produce a type instance.
The internal representation of type definitions is as follows:
nameTypeDefIR = nameTypeIR
aliasTypeDefIR = aliasTypeIR
dataTypeDefIR
: structTypeDefIR
| headerTypeDefIR
| headerUnionTypeDefIR
| enumTypeDefIR
;
objectTypeDefIR
: externObjectTypeDefIR
| parserObjectTypeDefIR
| controlObjectTypeDefIR
| packageObjectTypeDefIR
| tableObjectTypeDefIR
;
typeDefIR
: nameTypeDefIR
| aliasTypeDefIR
| dataTypeDefIR
| objectTypeDefIR
;
For example, structTypeDeclaration introduces a struct type definition,
structTypeDefIR:
structTypeDefIR
: STRUCT typeId `< typeParameterIR* > `{ fieldTypeIR* }
;
structTypeDeclaration
: annotationList STRUCT name typeParameterListOpt `{ typeFieldList }
;
Some type definitions like struct and header type definitions can be polymorphic, meaning they can have type variables as parameters. On the other hand, type definitions such as enum and type alias definitions can only be monomorphic. Below table summarizes which kinds of type definitions can be polymorphic:
8.3.2. Type resoultion
Notice that P4 has struct types, but the surface syntax does not have an explicit construct for struct types. Instead, struct types are always referenced by name. To easily handle named types in P4IR, the internal representation of types contain representations for the referred types.
structTypeIR
: STRUCT typeId `< typeArgumentIR* > `{ fieldTypeIR* }
;
For example, a struct type is represented as structTypeIR in P4IR. Thus,
after type checking, a named type in P416 is resolved to its referred type in
P4IR. For named types referring to monomorphic type definitions, the resolved
type is simply the type defined by the type definition.
enum E_t { A, B, C; }
void f(E_t e) {}
In the above example, the named type E_t is resolved to the enum type
enumTypeIR.
struct S<T> { T t; }
void f(S<bit<32>> s) {}
In the above example, the named type S<bit<32>> is resolved to a
structTypeIR, where it specializes a structTypeDefIR with the type argument
bit<32>. Type specialization is discussed in detail in the next section.
8.3.3. Type specialization
Click to view the specification source
def $specialize_typeDefIR(typeDefIR_base, typeArgumentIR*) = typeIR_spec
-- if (typeParameterIR_expl*, typeParameterIR_impl*) = $typeParameterListIR_of_typeDefIR(typeDefIR_base)
-- if typeParameterIR* = typeParameterIR_expl* ++ typeParameterIR_impl*
-- if |typeParameterIR*| = |typeArgumentIR*|
-- if theta = `{(typeParameterIR : typeArgumentIR)*}
-- if typeIR_base = $typeIR_of_typeDefIR(typeDefIR_base)
-- if typeIR_spec = $subst_typeIR(theta, typeIR_base)
8.3.4. Type names
prefixedTypeName
: typeName
| `TID . typeName
;
identifier
: `ID text
;
A prefixedTypeName and identifier may refer to either a monomorphic type
definition or a polymorphic type definition without type arguments. The names
can be prefixed by a dot (.) to refer to the top-level namespace.
On type checking, the referenced type definition is looked up by name. If the referenced type definition is monomorphic, then the resolved type is simply the type defined by the type definition. If the referenced type definition is polymorphic (but without type arguments), then the resolved type is a specialized type with no type arguments.
In some cases, the reference may be a type parameter. In other words, the type is indeed a variable type, not a concrete type until it is bound to a type argument. These are internally represented as:
nameTypeDefIR = nameTypeIR
8.3.4.1. Operations
Because functions and methods can be generic, they offer the possibility of declaring values with types that are type variables:
void f<T>() {
T x; // the type of x is T, a type variable
}
The type of such objects is not known until the function is specialized with specific type arguments.
Currently the only operations that are available for such values are assignment
(explicit through =, or implicit, through argument passing). This behavior is
similar to languages such as Java, and different from languages such as C++.
A future version of P4 may introduce a notion of type constraints which would enable more operations on such values. Because of this limitation, such values are currently of limited utility.
8.3.4.2. Type checking
Click to view the specification source
rulegroup Type_ok/prefixedTypeName-identifier: rule Type_ok/identifier: p TC |- `ID typeId : typeIR # typeId_fresh* -- Type_ok: p TC |- `TID typeId : typeIR # typeId_fresh* rule Type_ok/monomorphic: p TC |- prefixedTypeName : typeIR # eps -- if prefixedNameIR = $prefixedTypeName(prefixedTypeName) -- if typeDefIR = $find_typeDef_t(p, TC, prefixedNameIR) -- if $is_monomorphic_typeDefIR(typeDefIR) -- if typeIR = $typeIR_of_typeDefIR(typeDefIR) rule Type_ok/polymorphic: p TC |- prefixedTypeName : typeIR # eps -- if prefixedNameIR = $prefixedTypeName(prefixedTypeName) -- if typeDefIR = $find_typeDef_t(p, TC, prefixedNameIR) -- if ~$is_monomorphic_typeDefIR(typeDefIR) -- if typeIR = $specialize_typeDefIR(typeDefIR, eps)
8.3.5. Specialized types
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.
specializedType
: prefixedTypeName `< typeArgumentList >
;
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.
extern Register<T> {
Register(bit<32> size);
T read(bit<32> index);
void write(bit<32> index, T value);
}
The type T has to be specified when instantiating a set of registers, by
specializing the Register type:
Register<bit<32>>(128) registerBank;
The instantiation of registerBank is made using the Register type
specialized with the bit<32> bound to the T type argument.
8.3.5.1. Type checking
Click to view the specification source
rulegroup Type_ok/specializedType: rule Type_ok/specializedType: p TC |- prefixedTypeName `<typeArgumentList> : typeIR # typeId_fresh* -- if prefixedNameIR = $prefixedTypeName(prefixedTypeName) -- if typeDefIR = $find_typeDef_t(p, TC, prefixedNameIR) -- if $is_polymorphic_typeDefIR(typeDefIR) -- TypeArgumentList_ok: p TC |- typeArgumentList : typeIR_arg* # typeId_fresh* -- if typeIR = $specialize_typeDefIR(typeDefIR, typeIR_arg*)
8.4. Data types and values
dataTypeIR
: listTypeIR
| tupleTypeIR
| headerStackTypeIR
| structTypeIR
| headerTypeIR
| headerUnionTypeIR
| enumTypeIR
;
dataValue
: listValue
| tupleValue
| headerStackValue
| structValue
| headerValue
| headerUnionValue
| enumValue
;
Data types represent the types that aggregate other types, including lists, tuples, header stacks, structs, headers, header unions, and enums.
8.4.1. Lists
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
list<T>
listType
: LIST `< typeArgument >
;
Internally, list types and values are represented as follows:
listTypeIR
: LIST `< typeIR >
;
listValue
: LIST `[ value* ]
;
8.4.1.1. Operations
The value of a list is written using curly braces, with each element separated
by a comma. The left curly brace is preceded by a (list<T>) where T is the
list element type. Such a value can be passed as an argument, e.g. to extern
constructor functions.
struct pair_t {
bit<16> a;
bit<32> b;
}
extern E {
E(list<pair_t> data);
void run();
}
control c() {
E((list<pair_t>) {{2, 3}, {4, 5}}) e;
apply {
e.run();
}
}
Additionally, the size of a list can be determined at compile-time ([sec-minsizeinbits]).
8.4.1.2. Type checking
Click to view the specification source
rulegroup Type_ok/listType: rule Type_ok/listType: p TC |- LIST `<typeArgument> : (LIST `<typeIR_arg>) # typeId_fresh* -- TypeArgument_ok: p TC |- typeArgument : typeIR_arg # typeId_fresh*
8.4.1.3. Well-formedness
Click to view the specification source
rulegroup Type_wf/listTypeIR: rule Type_wf/listTypeIR: B |- LIST `<typeIR> -- if $nestable_list(typeIR) -- Type_wf: B |- typeIR
8.4.2. Tuples
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
tuple<T1, /* more fields omitted */, Tn>
The type tuple<> is a tuple type with no components.
tupleType
: TUPLE `< typeArgumentList >
;
Internally, tuple types and values are represented as follows:
tupleTypeIR
: TUPLE `< typeIR* >
;
tupleValue
: TUPLE `( value* )
;
8.4.2.1. Operations
The fields of a tuple can be accessed using array index syntax x[0], x[1].
The indexes must be local compile-time known values, to enable the
type-checker to identify the field types statically.
Tuples can be compared for equality using == and !=; two tuples are equal
if and only if all their fields are respectively equal.
Currently tuple fields are not left-values, even if the tuple itself is. (i.e., a tuple can only be assigned monolithically, and the field values cannot be changed individually.) This restriction may be lifted in a future version of the language.
8.4.2.2. Type checking
Click to view the specification source
rulegroup Type_ok/tupleType: rule Type_ok/tupleType: p TC |- TUPLE `<typeArgumentList> : (TUPLE `<typeIR_arg*>) # typeId_fresh* -- TypeArgumentList_ok: p TC |- typeArgumentList : typeIR_arg* # typeId_fresh*
8.4.2.3. Well-formedness
Click to view the specification source
rulegroup Type_wf/tupleTypeIR: rule Type_wf/tupleTypeIR: B |- TUPLE `<typeIR*> -- (if $nestable_tuple(typeIR))* -- (Type_wf: B |- typeIR)*
8.4.3. Header stacks
A header stack represents an array of headers or header unions. A header stack type is defined as:
headerStackType
: namedType `[ expression ]
;
where namedType refers to 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.
Internally, header stack types and values are represented as follows:
headerStackTypeIR
: typeIR `[ nat ]
;
headerStackValue
: HEADER_STACK `[ value* `( nat ; nat ) ]
;
At runtime a stack contains n values with type namedType, only some of
which may be valid. The valid elements of a header stack need not be
contiguous.
For example, the following declarations,
header Mpls_h {
bit<20> label;
bit<3> tc;
bit bos;
bit<8> ttl;
}
Mpls_h[10] mpls;
introduce a header stack called mpls containing ten entries, each of type
Mpls_h.
8.4.3.1. Operations
P4 provides a set of computations for manipulating header stacks. A header
stack hs of type h[n] can be understood in terms of the following
pseudocode:
// type declaration
struct hs_t {
bit<32> nextIndex;
bit<32> size;
h[n] data; // Ordinary array
}
// instance declaration and initialization
hs_t hs;
hs.nextIndex = 0;
hs.size = n;
Intuitively, a header stack can be thought of as a struct containing an
ordinary array of headers hs and a counter nextIndex that can be used to
simplify the construction of parsers for header stacks, as discussed below. The
nextIndex counter is initialized to 0.
Given a header stack value hs of size n, the following expressions are
legal:
-
hs[index]: produces a reference to the header at the specified position within the stack; ifhsis an l-value, the result is also an l-value. The header may be invalid. Some implementations may impose the constraint that the index expression must be a compile-time known value. A P4 compiler must give an error if an index that is a compile-time known value is out of range.Accessing a header stack
hswith an index less than0or greater than or equal tohs.sizeresults in an undefined value. See [sec-uninitialized-values-and-writing-invalid-headers] for more details.The
indexis an expression that must be of numeric types ([sec-numeric-values]). -
hs.size: produces a 32-bit unsigned integer that returns the size of the header stack (a local compile-time known value). -
assignment from a header stack
hsinto another stack requires the stacks to have the same types and sizes. All components ofhsare copied, including its elements and their validity bits, as well asnextIndex.
To help programmers write parsers for header stacks, P4 also offers computations that automatically advance through the stack as elements are parsed:
-
hs.next: produces a reference to the element with indexhs.nextIndexin the stack. May only be used in aparser. If the stack’snextIndexcounter is greater than or equal tosize, then evaluating this expression results in a transition torejectand sets the error toerror.StackOutOfBounds. Ifhsis an l-value, thenhs.nextis also an l-value. -
hs.last: produces a reference to the element with indexhs.nextIndex - 1in the stack, if such an element exists. May only be used in aparser. If thenextIndexcounter is less than1, or greater thansize, then evaluating this expression results in a transition torejectand sets the error toerror.StackOutOfBounds. Unlikehs.next, the resulting reference is never an l-value. -
hs.lastIndex: produces a 32-bit unsigned integer that encodes the indexhs.nextIndex - 1. May only be used in aparser. If thenextIndexcounter is0, then evaluating this expression produces an undefined value.
Finally, P4 offers the following computations that can be used to manipulate the elements at the front and back of the stack:
-
hs.push_front(int count): shiftshs"right" bycount. The firstcountelements become invalid. The lastcountelements in the stack are discarded. Thehs.nextIndexcounter is incremented bycount. Thecountargument must be a compile-time known value that is a positive integer. The return type isvoid. -
hs.pop_front(int count): shiftshs"left" bycount(i.e., element with indexcountis copied in stack at index0). The lastcountelements become invalid. Thehs.nextIndexcounter is decremented bycount. Thecountargument must be a compile-time known value that is a positive integer. The return type isvoid.
The following pseudocode defines the behavior of push_front and pop_front:
void push_front(int count) {
for (int i = this.size-1; i >= 0; i -= 1) {
if (i >= count) {
this[i] = this[i-count];
} else {
this[i].setInvalid();
}
}
this.nextIndex = this.nextIndex + count;
if (this.nextIndex > this.size) this.nextIndex = this.size;
// Note: this.last, this.next, and this.lastIndex adjust with this.nextIndex
}
void pop_front(int count) {
for (int i = 0; i < this.size; i++) {
if (i+count < this.size) {
this[i] = this[i+count];
} else {
this[i].setInvalid();
}
}
if (this.nextIndex >= count) {
this.nextIndex = this.nextIndex - count;
} else {
this.nextIndex = 0;
}
// Note: this.last, this.next, and this.lastIndex adjust with this.nextIndex
}
Similar to structs and headers, the size of a header stack is a compile-time known value (Section [sec-minsizeinbits]).
Two header stacks can be compared for equality (==) or inequality (!=) only
if they have the same element type and the same length. Two stacks are equal if
and only if all their corresponding elements are equal. Note that the
nextIndex value is not used in the equality comparison.
8.4.3.2. Type checking
Click to view the specification source
rulegroup Type_ok/headerStackType: rule Type_ok/headerStackType: p TC |- namedType `[expression_size] : (typeIR_base `[n_size]) # typeId_fresh* -- Type_ok: p TC |- namedType : typeIR_base # typeId_fresh* -- Expr_ok: p TC |- expression_size : typedExpressionIR_size -- if LCTK = $ctk_of_typedExpressionIR(typedExpressionIR_size) -- Expr_eval_lctk: p TC |- typedExpressionIR_size ~> integerValue_size -- if n_size = $nat_of_integerValue(integerValue_size) -- if n_size > 0
8.4.3.3. Well-formedness
Click to view the specification source
rulegroup Type_wf/headerStackTypeIR: rule Type_wf/headerStackTypeIR: B |- typeIR `[_] -- if $nestable_headerStack(typeIR) -- Type_wf: B |- typeIR
8.4.4. Arrays
8.4.5. Structs
P4 struct types are defined with the following syntax:
typeField
: annotationList type name ;
;
structTypeDeclaration
: annotationList STRUCT name typeParameterListOpt `{ typeFieldList }
;
This declaration introduces a new struct type definition with the specified name in the current scope. Field names have to be distinct. An empty struct (with no fields) is legal.
structTypeDefIR
: STRUCT typeId `< typeParameterIR* > `{ fieldTypeIR* }
;
For example, the structure Parsed_headers below contains the headers
recognized by a simple parser:
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;
}
Struct types can be used by referring to their names. Generic struct types can be used by specializing their type parameters with type arguments. Internally, struct types and values are represented as follows:
fieldTypeIR
: annotationList typeIR nameIR ;
;
structTypeIR
: STRUCT typeId `< typeArgumentIR* > `{ fieldTypeIR* }
;
fieldValue
: value nameIR ;
;
structValue
: STRUCT typeId `{ fieldValue* }
;
8.4.5.1. Operations
The only operation defined on expressions whose type is a struct is field
access, written using dot (".") notation—e.g., s.field. If s is an
l-value, then s.field is also an l-value. P4 also allows copying structs
using assignment when the source and target of the assignment have the same
type. Finally, structs can be initialized with a sequence expression, as
discussed in Section 8.6.3, or with a record expression, as described in
Section 8.6.4. Both of these cases must initialize all fields of the
structure. The size of a struct can be determined at compile-time
([sec-minsizeinbits]).
Two structs can be compared for equality (==) or inequality (!=) only if
they have the same type and all of their fields can be recursively compared for
equality. Two structures are equal if and only if all their corresponding
fields are equal.
The following example shows a structure initialized in several different ways:
struct S {
bit<32> a;
bit<32> b;
}
const S x = { 10, 20 }; // sequence expression
const S x = { a = 10, b = 20 }; // record expression
const S x = (S) { a = 10, b = 20 }; // record expression
See [sec-uninitialized-values-and-writing-invalid-headers] for a description
of the behavior if struct fields are read without being initialized.
8.4.5.2. Type checking
Struct types are introduced by struct declarations. Its typing rule is
explained in Section 11.14. Struct types are then used by
referencing their names. Thus struct types can be obtained from named types,
as illustrated in Section 8.3.
8.4.5.3. Well-formedness
Click to view the specification source
rulegroup Type_wf/structTypeIR:
rule Type_wf/structTypeIR:
B |- STRUCT _ `<_> `{(_ typeIR_field nameIR_field ;)*}
-- if $distinct_<nameIR>(nameIR_field*)
-- (if $nestable_struct(typeIR_field))*
-- (Type_wf: B |- typeIR_field)*
8.4.6. Headers
The declaration of a header type is given by the following syntax:
typeField
: annotationList type name ;
;
headerTypeDeclaration
: annotationList HEADER name typeParameterListOpt `{ typeFieldList }
;
where each type 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 definition can be referred to using this identifier.
headerTypeDefIR
: HEADER typeId `< typeParameterIR* > `{ fieldTypeIR* }
;
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().
Header types may be empty:
header Empty_h { }
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.
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;
}
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:
header Ethernet_h {
bit<48> dstAddr;
bit<48> srcAddr;
bit<16> etherType;
}
The following variable declaration uses the newly introduced type Ethernet_h:
Ethernet_h ethernetHeader;
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
[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:
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;
}
As demonstrated by a code example in [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.
Internally, header types and values are represented as follows:
headerTypeIR
: HEADER typeId `< typeArgumentIR* > `{ fieldTypeIR* }
;
headerValue
: HEADER typeId `{ bool ; fieldValue* }
;
8.4.6.1. Operations
Headers provide the same operations as structs. Assignment between headers
also copies the "validity" header bit.
In addition, headers support the following methods:
-
The method
isValid()returns the value of the "validity" bit of the header. -
The method
setValid()sets the header’s validity bit to "true". It can only be applied to an l-value. -
The method
setInvalid()sets the header’s validity bit to "false". It can only be applied to an l-value.
Similar to a struct, a header object can be initialized with a sequence
expression (see Section 8.6.3) — the sequence elements are assigned to the
header fields in the order they appear — or with a record expression (see
Section 8.6.4). When initialized the header automatically becomes valid:
header H { bit<32> x; bit<32> y; }
H h;
h = { 10, 12 }; // This also makes the header h valid
h = { y = 12, x = 10 }; // Same effect as above
Two headers can be compared for equality (==) or inequality (!=) only if
they have the same type. Two headers are equal if and only if they are both
invalid, or they are both valid and all their corresponding fields are equal.
Furthermore, the size of a header can be determined at compile-time
([sec-minsizeinbits]).
See [sec-uninitialized-values-and-writing-invalid-headers] for a description of the behavior if header fields are read without being initialized, or header fields are written to a currently invalid header.
8.4.6.2. Type checking
Header types are introduced by header declarations. Its typing rule is
explained in Section 11.15. Header types are then used by
referencing their names. Thus header types can be obtained from named types,
as illustrated in Section 8.3.
8.4.6.3. Well-formedness
Click to view the specification source
rulegroup Type_wf/headerTypeIR:
rule Type_wf/headerTypeIR:
B |- HEADER _ `<_> `{(_ typeIR_field nameIR_field ;)*}
-- if $distinct_<nameIR>(nameIR_field*)
-- (if $nestable_header(typeIR_field))*
-- (Type_wf: B |- typeIR_field)*
8.4.7. 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:
typeField
: annotationList type name ;
;
headerUnionTypeDeclaration
: annotationList HEADER_UNION name typeParameterListOpt `{ typeFieldList }
;
This declaration introduces a new type definition with the specified name in the current scope.
headerUnionTypeDefIR
: HEADER_UNION typeId `< typeParameterIR* > `{ fieldTypeIR* }
;
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:
header_union IP_h {
IPv4_h v4;
IPv6_h v6;
}
A header union is not considered a type with fixed length.
Internally, header union types and values are represented as follows:
headerUnionTypeIR
: HEADER_UNION typeId `< typeArgumentIR* > `{ fieldTypeIR* }
;
headerUnionValue
: HEADER_UNION typeId `{ fieldValue* }
;
8.4.7.1. Operations
A variable declared with a union type is initially invalid. For example:
header H1 {
bit<8> f;
}
header H2 {
bit<16> g;
}
header_union U {
H1 h1;
H2 h2;
}
U u; // u invalid
This also implies that each of the headers h1 through hn contained in a
header union are also initially invalid. Unlike headers, a union cannot be
initialized. However, the validity of a header union can be updated by
assigning a valid header to one of its elements:
U u;
H1 my_h1 = { 8w0 }; // my_h1 is valid
u.h1 = my_h1; // u and u.h1 are both valid
We can also assign a sequence to an element of a header union,
U u;
u.h2 = { 16w1 }; // u and u.h2 are both valid
or set their validity bits directly.
U u;
u.h1.setValid(); // u and u.h1 are both valid
H1 my_h1 = u.h1; // my_h1 is now valid, but contains an undefined value
Note that reading an uninitialized header produces an undefined value, even if the header is itself valid.
If u is an expression whose type is a header union U with fields ranged
over by hi, then the expression u.hi evaluates to a header, and thus it can
be used wherever a header expression is allowed. If u is a left-value, then
u.hi is also a left-value.
The following operations:
-
u.hi.setValid():sets the valid bit for headerhitotrueand sets the valid bit for all other headers tofalse, which implies that it is unspecified what value reading any member header ofuwill return. -
u.hi.setInvalid(): if the valid bit for any member header ofuistruethen sets it tofalse, which implies that it is unspecified what value reading any member header ofuwill return.
The assignment to a union field:
u.hi = e;
has the following meaning:
-
if
eis valid, then it is equivalent to:
u.hi.setValid();
u.hi = e;
-
if
eis invalid, then it is equivalent to:
u.hi.setInvalid();
Assignments between variables of the same type of header union are permitted.
The assignment u1 = u2 copies the full state of header union u2 to u1. If
u2 is valid, then there is some header u2.hi that is valid. The assignment
behaves the same as u1.hi = u2.hi. If u2 is not valid, then u1 becomes
invalid (i.e. if any header of u1 was valid, it becomes invalid).
u.isValid() returns true if any member of the header union u is valid,
otherwise it returns false. setValid() and setInvalid() methods are not
defined for header unions.
Supplying an expression with a union type to emit simply emits the single
header that is valid, if any.
The following example shows how we can use header unions to represent IPv4 and IPv6 headers uniformly:
header_union IP {
IPv4 ipv4;
IPv6 ipv6;
}
struct Parsed_packet {
Ethernet ethernet;
IP ip;
}
parser top(packet_in b, out Parsed_packet p) {
state start {
b.extract(p.ethernet);
transition select(p.ethernet.etherType) {
16w0x0800 : parse_ipv4;
16w0x86DD : parse_ipv6;
}
}
state parse_ipv4 {
b.extract(p.ip.ipv4);
transition accept;
}
state parse_ipv6 {
b.extract(p.ip.ipv6);
transition accept;
}
}
As another example, we can also use unions to parse (selected) TCP options:
header Tcp_option_end_h {
bit<8> kind;
}
header Tcp_option_nop_h {
bit<8> kind;
}
header Tcp_option_ss_h {
bit<8> kind;
bit<32> maxSegmentSize;
}
header Tcp_option_s_h {
bit<8> kind;
bit<24> scale;
}
header Tcp_option_sack_h {
bit<8> kind;
bit<8> length;
varbit<256> sack;
}
header_union Tcp_option_h {
Tcp_option_end_h end;
Tcp_option_nop_h nop;
Tcp_option_ss_h ss;
Tcp_option_s_h s;
Tcp_option_sack_h sack;
}
typedef Tcp_option_h[10] Tcp_option_stack;
struct Tcp_option_sack_top {
bit<8> kind;
bit<8> length;
}
parser Tcp_option_parser(packet_in b, out Tcp_option_stack vec) {
state start {
transition select(b.lookahead<bit<8>>()) {
8w0x0 : parse_tcp_option_end;
8w0x1 : parse_tcp_option_nop;
8w0x2 : parse_tcp_option_ss;
8w0x3 : parse_tcp_option_s;
8w0x5 : parse_tcp_option_sack;
}
}
state parse_tcp_option_end {
b.extract(vec.next.end);
transition accept;
}
state parse_tcp_option_nop {
b.extract(vec.next.nop);
transition start;
}
state parse_tcp_option_ss {
b.extract(vec.next.ss);
transition start;
}
state parse_tcp_option_s {
b.extract(vec.next.s);
transition start;
}
state parse_tcp_option_sack {
bit<8> n = b.lookahead<Tcp_option_sack_top>().length;
// n is the total length of the TCP SACK option in bytes.
// The length of the varbit field 'sack' of the
// Tcp_option_sack_h header is thus n-2 bytes.
b.extract(vec.next.sack, (bit<32>) (8 * n - 16));
transition start;
}
}
Similar to headers, the size of a header union is a local compile-time known value (Section [sec-minsizeinbits]).
Two header unions can be compared for equality (==) or inequality (!=) if
they have the same type. The unions are equal if and only if all their
corresponding fields are equal (i.e., either all fields are invalid in both
unions, or in both unions the same field is valid, and the values of the valid
fields are equal as headers).
8.4.7.2. Type checking
Header union types are introduced by header_union declarations. Its typing
rule is explained in Section 11.16. Header union types
are then used by referencing their names. Thus header union types can be
obtained from named types, as illustrated in Section 8.3.
8.4.7.3. Well-formedness
Click to view the specification source
rulegroup Type_wf/headerUnionTypeIR:
rule Type_wf/headerUnionTypeIR:
B |- HEADER_UNION _ `<_> `{(_ typeIR_field nameIR_field ;)*}
-- if $distinct_<nameIR>(nameIR_field*)
-- (if $nestable_headerUnion(typeIR_field))*
-- (Type_wf: B |- typeIR_field)*
8.4.8. Enums
An enumeration type is defined using the following syntax:
namedExpression
: name = expression
;
namedExpressionList
: namedExpression
| namedExpressionList , namedExpression
;
enumTypeDeclaration
: annotationList ENUM name `{ nameList trailingCommaOpt }
| annotationList ENUM type name `{ namedExpressionList trailingCommaOpt }
;
An enum declaration introduces a new enum type definition:
enumTypeDefIR = enumTypeIR
Enums cannot be parameterized over type parameters, thus is always monomorphic.
Enums can be declared in two different ways, depending on whether or not an underlying representation type is provided. The two forms are described below.
Internally, enum types and values are represented as follows:
valueFieldIR
: nameIR = value ;
;
enumTypeIR
: simpleEnumTypeIR
| serializableEnumTypeIR
;
enumValue
: typeId . nameIR
| typeId . nameIR . value
;
8.4.8.1. Enum type declaration without an underlying type
For example, the declaration
enum Suits { Clubs, Diamonds, Hearths, Spades }
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).
8.4.8.2. Enum type declaration with an underlying type
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.
For example, the declaration
enum bit<16> EtherType {
VLAN = 0x8100,
QINQ = 0x9100,
MPLS = 0x8847,
IPV4 = 0x0800,
IPV6 = 0x86dd
}
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
enum bit<8> FailingExample {
first = 1,
second = 2,
third = 3,
unrepresentable = 300
}
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 local compile-time known value.
Annotations, represented by the non-terminal annotationList, are described in
[sec-annotations].
8.4.8.3. Operations
Symbolic names declared by an enum belong to the namespace introduced by the enum declaration rather than the top-level namespace.
enum X { v1, v2, v3 }
X.v1 // reference to v1
v1 // error - v1 is not in the top-level namespace
Similar to errors, enum expressions without a specified underlying type only
support equality (==) and inequality (!=) comparisons. Expressions whose
type is an enum without a specified underlying type cannot be cast to or from
any other type.
An enum may also specify an underlying type, such as the following:
enum bit<8> E {
e1 = 0,
e2 = 1,
e3 = 2
}
More than one symbolic value in an enum may map to the same fixed-width
integer value.
enum bit<8> NonUnique {
b1 = 0,
b2 = 1, // Note, both b2 and b3 map to the same value.
b3 = 1,
b4 = 2
}
An enum with an underlying type also supports explicit casts to and from the
underlying type. For instance, the following code:
bit<8> x;
E a = E.e2;
E b;
x = (bit<8>) a; // sets x to 1
b = (E) x; // sets b to E.e2
casts a (which was initialized with E.e2) to a bit<8>, using the
specified fixed-width unsigned integer representation for E.e2, i.e. 1. The
variable b is then set to the symbolic value E.e2, which corresponds to the
fixed-width unsigned integer value 1.
Because it is always safe to cast from an enum to its underlying fixed-width
integer type, implicit casting from an enum to its fixed-width (signed or
unsigned) integer type is also supported (see Section 17.2):
bit<8> x = E.e2; // sets x to 1 (E.e2 is automatically casted to bit<8>)
E a = E.e2
bit<8> y = a << 3; // sets y to 8 (a is automatically casted to bit<8> and then shifted)
Implicit casting from an underlying fixed-width type to an enum is not supported.
enum bit<8> E1 {
e1 = 0, e2 = 1, e3 = 2
}
enum bit<8> E2 {
e1 = 10, e2 = 11, e3 = 12
}
E1 a = E1.e1;
E2 b = E2.e2;
a = b; // Error: b is automatically casted to bit<8>,
// but bit<8> cannot be automatically casted to E1
a = (E1) b; // OK
a = E1.e1 + 1; // Error: E.e1 is automatically casted to bit<8>,
// and the right-hand expression has
// the type bit<8>, which cannot be casted to E automatically.
a = (E1)(E1.e1 + 1); // Final explicit casting makes the assignment legal
a = E1.e1 + E1.e2; // Error: both arguments to the addition are automatically
// casted to bit<8>. Thus the addition itself is legal, but
// the assignment is not
a = (E1)(E2.e1 + E2.e2); // Final explicit casting makes the assignment legal
A reasonable compiler might generate a warning in cases that involve multiple automatic casts.
E1 a = E1.e1;
E2 b = E2.e2;
bit<8> c;
if (a > b) { // Potential warning: two automatic and different casts to bit<8>.
// code omitted
}
c = a + b; // Legal, but a warning would be reasonable
Note that while it is always safe to cast from an enum to its fixed-width
unsigned integer type, and vice versa, there may be cases where casting a
fixed-width unsigned integer value to its related enum type produces an
unnamed value.
bit<8> x = 5;
E e = (E) x; // sets e to an unnamed value
sets e to an unnamed value, since there is no symbol corresponding to the
fixed-width unsigned integer value 5.
For example, in the following code, the else clause of the if/else if/else
block can be reached even though the matches on x are complete with respect
to the symbols defined in MyPartialEnum_t:
enum bit<2> MyPartialEnum_t {
VALUE_A = 2w0,
VALUE_B = 2w1,
VALUE_C = 2w2
}
bit<2> y = < some value >;
MyPartialEnum_t x = (MyPartialEnum_t)y;
if (x == MyPartialEnum_t.VALUE_A) {
// some code here
} else if (x == MyPartialEnum_t.VALUE_B) {
// some code here
} else if (x == MyPartialEnum_t.VALUE_C) {
// some code here
} else {
// A P4 compiler MUST ASSUME that this branch can be executed
// some code here
}
Additionally, if an enumeration is used as a field of a header, we would expect
the transition select to match default when the parsed integer value does
not match one of the symbolic values of EtherType in the following example:
enum bit<16> EtherType {
VLAN = 0x8100,
IPV4 = 0x0800,
IPV6 = 0x86dd
}
header ethernet {
// Some fields omitted
EtherType etherType;
}
parser my_parser(/* parameters omitted */) {
state parse_ethernet {
packet.extract(hdr.ethernet);
transition select(hdr.ethernet.etherType) {
EtherType.VLAN : parse_vlan;
EtherType.IPV4 : parse_ipv4;
EtherType.IPV6: parse_ipv6;
default: reject;
}
}
Any variable with an enum type that contains an unnamed value (whether as the
result of a cast to an enum with an underlying type, parse into the field of
an enum with an underlying type, or simply the declaration of any enum
without a specified initial value) will not be equal to any of the values
defined for that type. Such an unnamed value should still lead to predictable
behavior in cases where any legal value would match, e.g. it should match in
any of these situations:
-
If used in a
selectexpression, it should matchdefaultor_in a key set expression. -
If used as a key with
match_kindternaryin a table, it should match a table entry where the field has all bit positions "don’t care". -
If used as a key with
match_kindlpmin a table, it should match a table entry where the field has a prefix length of 0.
Note that if an enum value lacking an underlying type appears in the
control-plane API, the compiler must select a suitable serialization data type
and representation. For enum values with an underlying type and
representations, the compiler should use the specified underlying type as the
serialization data type and representation.
Additionally, the size of a serializable enum can be determined at compile-time. However, the size of an enum without an underlying type cannot be determined at compile-time ([sec-minsizeinbits]).
8.4.8.4. Type checking
Enum types are introduced by enum declarations. Its typing rule is explained
in Section 11.13. Enum types are then used by referencing their
names. Thus enum types can be obtained from named types, as illustrated in
Section 8.3.
8.4.8.5. Well-formedness
Click to view the specification source
rulegroup Type_wf/enumTypeIR:
rule Type_wf/non-serializable:
B |- ENUM _ `{nameIR_field*}
-- if $distinct_<nameIR>(nameIR_field*)
rule Type_wf/serializable:
B |- ENUM _ `<typeIR> `{(nameIR_field = _ ;)*}
-- if $distinct_<nameIR>(nameIR_field*)
-- if $nestable_enum_serializable(typeIR)
-- Type_wf: B |- typeIR
8.5. Object types and values
objectTypeIR
: externObjectTypeIR
| parserObjectTypeIR
| controlObjectTypeIR
| packageObjectTypeIR
| tableObjectTypeIR
;
objectReferenceValue
: REF objectId
;
Stateful objects are represented using objectTypeIR. At runtime, these
objects are passed along directionless parameters as references
(objectReferenceValue).
The types parser, control, and package cannot be used as types of
arguments for actions, functions, methods, parsers, controls, or tables. They
can be used as types for the arguments passed to constructors.
8.5.1. Extern object types
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 declarations may only appear as allowed by the architecture model and may be specific to a target.
externConstructorPrototype
: annotationList typeIdentifier `( parameterList ) ;
;
externMethodPrototype
: annotationList functionPrototype ;
| annotationList ABSTRACT functionPrototype ;
;
externConstructorOrMethodPrototype
: externConstructorPrototype
| externMethodPrototype
;
externObjectDeclaration
: annotationList EXTERN nonTypeName typeParameterListOpt
`{ externConstructorOrMethodPrototypeList }
;
For example, the P4 core library introduces two extern objects packet_in and
packet_out used for manipulating packets (see [sec-packet-data-extraction]
and [sec-deparse]). Here is an example showing how the methods of these
objects can be invoked on a packet:
extern packet_out {
void emit<T>(in T hdr);
}
control d(packet_out b, in Hdr h) {
apply {
b.emit(h.ipv4); // write ipv4 header into output packet
} // by calling emit method
}
An extern object declaration introduces a new extern object type definition.
externObjectTypeDefIR
: EXTERN typeId `< typeParameterIR* , typeParameterIR* >
externMethodTypeDefEnv
;
Extern object types can be referred to using their name. Internally, extern object types are represented as:
externObjectTypeIR
: EXTERN typeId `< typeArgumentIR* > externMethodTypeDefEnv
;
8.5.1.1. Type checking
Extern object types are introduced by extern declarations. Its typing rule is
explained in [sem-extern-object-declaration]. Extern object types are then
used by referencing their names. Thus extern object types can be obtained from
named types, as illustrated in Section 8.3.
8.5.1.2. Well-formedness
Click to view the specification source
rulegroup Type_wf/externObjectTypeIR:
rule Type_wf/externObjectTypeIR:
B |- EXTERN _ `<_> (`{(_ : externMethodTypeDefIR)*})
-- (CallableTypeDef_wf: B |- externMethodTypeDefIR)*
8.5.2. Parser object types
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.
parserTypeDeclaration
: annotationList PARSER name typeParameterListOpt `( parameterList ) ;
;
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
struct Counters { /* Fields omitted */ }
parser P<H>(packet_in b,
out H packetHeaders,
out Counters counters);
A parser type declaration introduces a new parser object type definition.
parserObjectTypeDefIR
: PARSER typeId `< typeParameterIR* , typeParameterIR* > `( parameterIR* )
;
Parser types can be referred to using their name and type arguments. Internally, parser types are represented as:
parserObjectTypeIR
: PARSER typeId `< typeArgumentIR* > `( parameterIR* )
;
8.5.2.1. Type checking
Parser object types are introduced by parser type declarations. Its typing rule is explained in Section 11.18. Parser object types are then used by referencing their names. Thus parser object types can be obtained from named types, as illustrated in Section 8.3.
8.5.2.2. Well-formedness
Click to view the specification source
rulegroup Type_wf/parserObjectTypeIR: rule Type_wf/parserObjectTypeIR: B |- PARSER _ `<_> `(parameterIR*) -- (ParameterType_wf: B |- parameterIR)*
8.5.3. Control object types
A control type declaration describes the signature of a control block.
controlTypeDeclaration
: annotationList CONTROL name typeParameterListOpt `( parameterList ) ;
;
Control type declarations are similar to parser type declarations.
A control type declaration introduces a new control object type definition.
controlObjectTypeDefIR
: CONTROL typeId `< typeParameterIR* , typeParameterIR* > `( parameterIR* )
;
Control types can be referred to using their name and type arguments. Internally, control types are represented as:
controlObjectTypeIR
: CONTROL typeId `< typeArgumentIR* > `( parameterIR* )
;
8.5.3.1. Type checking
Control object types are introduced by control type declarations. Its typing rule is explained in Section 11.19. Control object types are then used by referencing their names. Thus control object types can be obtained from named types, as illustrated in Section 8.3.
8.5.3.2. Well-formedness
Click to view the specification source
rulegroup Type_wf/controlObjectTypeIR: rule Type_wf/controlObjectTypeIR: B |- CONTROL _ `<_> `(parameterIR*) -- (ParameterType_wf: B |- parameterIR)*
8.5.4. Package object types
A package type describes the signature of a package.
packageTypeDeclaration
: annotationList PACKAGE name typeParameterListOpt `( parameterList ) ;
;
This introduces a new package object type definition.
packageObjectTypeDefIR
: PACKAGE typeId `< typeParameterIR* , typeParameterIR* > `{ typeIR* }
;
Package types can be referred to using their name and type arguments. Internally, package types are represented as:
packageObjectTypeIR
: PACKAGE typeId `< typeArgumentIR* > `{ typeIR* }
;
8.5.4.1. Type checking
Package object types are introduced by package declarations. Its typing rule
is explained in Section 11.20. Package object types are then
used by referencing their names. Thus package object types can be obtained from
named types, as illustrated in Section 8.3.
8.5.4.2. Well-formedness
Click to view the specification source
rulegroup Type_wf/packageObjectTypeIR:
rule Type_wf/packageObjectTypeIR:
B |- PACKAGE _ `<_> `{typeIR*}
-- (Type_wf: B |- typeIR)*
8.5.5. Table object types
A table declaration introduces a table instance and a table type definition.
tableDeclaration
: annotationList TABLE name `{ tablePropertyList }
;
tableObjectTypeDefIR = tableObjectTypeIR
Internally, table types are represented as:
tableObjectTypeIR
: TABLE typeId `{ tableMetadataStructTypeIR }
;
Table metadata types are introduced in [sec-table-metadata-type-and-value].
8.5.5.1. Well-formedness
Click to view the specification source
rulegroup Type_wf/tableObjectTypeIR:
rule Type_wf/tableObjectTypeIR:
B |- TABLE _ `{tableMetadataStructTypeIR}
-- Type_wf: B |- tableMetadataStructTypeIR
8.5.6. Object reference values
P4 objects are stateful, thus their values are references to the actual objects allocated in the global store.
objectId = nameIR*
objectReferenceValue
: REF objectId
;
Objects are referred to by their fully-qualified names from the top-level scope. === Alias types
A typedef or type declaration can be used to give an alias to a type.
typedef
: type
| derivedTypeDeclaration
;
typedefDeclaration
: annotationList TYPEDEF typedef name ;
| annotationList TYPE type name ;
;
Internally, the alias is represented as:
aliasTypeIR
: typedefTypeIR
| newTypeIR
;
8.5.7. typedef declaration
typedef bit<32> u32;
typedef struct Point { int<32> x; int<32> y; } Pt;
typedef Empty_h[32] HeaderStack;
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:
struct S<T> {
T field;
}
// typedef S X; -- illegal: S does not have type arguments
typedef S<bit<32>> X; // -- legal
Internally, the alias is represented as:
typedefTypeIR
: TYPEDEF typeId typeIR
;
8.5.7.1. Type checking
typedefs are introduced by typedef declarations. Its typing rule is
explained in Section 11.17.1. typedefs are then used by
referencing their names. Thus typedef types can be obtained from named types,
as illustrated in Section 8.3.
8.5.7.2. Well-formedness
Click to view the specification source
rulegroup Type_wf/typedefTypeIR: rule Type_wf/typedefTypeIR: B |- TYPEDEF _ typeIR -- if $nestable_typedef(typeIR) -- Type_wf: B |- typeIR
8.5.8. type declaration
Similarly to typedef, the keyword type can be used to introduce a new type.
type bit<32> U32;
U32 x = (U32)0;
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:
type bit<9> PortId_t;
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).
Internally, the alias is represented as:
newTypeIR
: TYPE typeId typeIR
;
8.5.8.1. Operations
Values with a type introduced by the type keyword provide only a few operations:
-
assignment to left-values of the same type
-
comparisons for equality and inequality if the original type supported such comparisons
-
casts to and from the original type
type bit<32> U32;
U32 x = (U32)0; // cast needed
U32 y = (U32) ((bit<32>)x + 1); // casts needed for arithmetic
bit<32> z = 1;
bool b0 = x == (U32)z; // cast needed
bool b1 = (bit<32>)x == z; // cast needed
bool b2 = x == y; // no cast needed
8.5.8.2. Type checking
types are introduced by type declarations. Its typing rule is explained
in Section 11.17.2. types are then used by referencing their
names. Thus type types can be obtained from named types, as illustrated in
Section 8.3.
8.5.8.3. Well-formedness
Click to view the specification source
rulegroup Type_wf/newTypeIR: rule Type_wf/newTypeIR: B |- TYPE _ typeIR -- if $nestable_new(typeIR) -- Type_wf: B |- typeIR
8.6. Synthesized types and values
For the purposes of type-checking the P4 compiler can synthesize some type representations which cannot be directly expressed by users.
These types and values are represented as:
synthesizedTypeIR
: defaultTypeIR
| invalidHeaderTypeIR
| sequenceTypeIR
| recordTypeIR
| setTypeIR
| tableMetadataTypeIR
;
synthesizedValue
: defaultValue
| invalidHeaderValue
| sequenceValue
| recordValue
| setValue
| tableMetadataValue
;
8.6.1. Default
A left-value can be initialized automatically with a default value of the suitable type using:
defaultExpression
: ...
;
Internally, the type and value of ... itself are represented as:
defaultTypeIR
: DEFAULT
;
defaultValue
: DEFAULT
;
8.6.1.1. Operations
For example:
struct S {
bit<32> b32;
bool b;
}
enum int<8> N0 {
one = 1,
zero = 0,
two = 2
}
enum N1 {
A, B, C, F
}
S s0 = ...; // initialize s0 with the default value { 0, false }
N0 n0 = ...; // initialize n0 with the default value 0
N1 n1 = ...; // initialize n1 with the default value N1.A
See Section 14.4 for semantics of default initialization.
8.6.1.2. Well-formedness
Click to view the specification source
rulegroup Type_wf/defaultTypeIR: rule Type_wf/defaultTypeIR: B |- DEFAULT
8.6.2. Invalid headers
The expression {#} represents an invalid header of some type, but it can be
any header or header union type. A P4 compiler may require an explicit cast on
this expression in cases where it cannot determine the particular header or
header union type from the context.
invalidHeaderExpression
: {#}
;
Note that the # character cannot be misinterpreted as a preprocessor
directive, since it cannot be the first character on a line when it occurs in
the single lexical token {#}, which may not have whitespace or any other
characters between those shown.
Internally, the type and value of {#} itself are represented as:
invalidHeaderTypeIR
: HEADER_INVALID
;
invalidHeaderValue
: {#}
;
8.6.2.1. Operations
For example:
header H { bit<32> x; bit<32> y; }
H h;
h = {#}; // This makes the header h become invalid
8.6.2.2. Well-formedness
Click to view the specification source
rulegroup Type_wf/invalidHeaderTypeIR: rule Type_wf/invalidHeaderTypeIR: B |- HEADER_INVALID
8.6.3. Sequences
A sequence expression is written using curly braces, with each element separated by a comma:
sequenceElementExpression = expressionList
sequenceOrRecordElementExpression
: sequenceElementExpression
| recordElementExpression
;
sequenceOrRecordExpression
: `{ sequenceOrRecordElementExpression trailingCommaOpt }
;
The type of a sequence expression is a sequence type. Its value is a sequence value.
sequenceTypeIR
: SEQ `< typeIR* >
| SEQ `< typeIR* , ... >
;
sequenceValue
: SEQ `( value* )
| SEQ `( value* , ... )
;
8.6.3.1. Operations
Sequence expressions can be assigned to expressions of type tuple, struct
or header, and can also be passed as arguments to methods. Sequences may be
nested.
For instance, sequences can be assigned to other tuples with the same type:
tuple<bit<32>, bool> x = { 10, false };
The following program fragment uses a sequence expression to pass several header fields simultaneously to a learning provider:
extern LearningProvider<T> {
LearningProvider();
void learn(in T data);
}
LearningProvider<tuple<bit<48>, bit<32>>>() lp;
lp.learn( { hdr.ethernet.srcAddr, hdr.ipv4.src } );
A sequence may be used to initialize a structure if the tuple has the same number of elements as fields in the structure. The effect of such an initializer is to assign the nth element of the tuple to the nth field in the structure:
struct S {
bit<32> a;
bit<32> b;
}
const S x = { 10, 20 }; // a = 10, b = 20
Sequence expressions that have ... as their last element are allowed to
give values to only a subset of the fields of the struct or header type to
which it evaluates. Any field names not given a value explicitly will be given
their default value (see [sec-initializing-with-default-values]).
8.6.3.2. Well-formedness
Click to view the specification source
rulegroup Type_wf/sequenceTypeIR: rule Type_wf/non-default: B |- SEQ `<typeIR*> -- (Type_wf: B |- typeIR)* rule Type_wf/default: B |- SEQ `<typeIR* , ...> -- (Type_wf: B |- typeIR)*
8.6.4. Records
One can write record expressions that evaluate to a structure or header. The syntax of these expressions is given by:
recordElementExpression
: name = expression
| name = expression , ...
| name = expression , namedExpressionList
| name = expression , namedExpressionList , ...
;
sequenceOrRecordElementExpression
: sequenceElementExpression
| recordElementExpression
;
sequenceOrRecordExpression
: `{ sequenceOrRecordElementExpression trailingCommaOpt }
;
Internally, the type and value of a record expression are represented as:
recordTypeIR
: RECORD `{ fieldTypeIR* }
| RECORD `{ fieldTypeIR* , ... }
;
recordValue
: RECORD `{ fieldValue* }
| RECORD `{ fieldValue* , ... }
;
8.6.4.1. Operations
The following example shows a structure-valued expression used in an equality comparison expression:
struct S {
bit<32> a;
bit<32> b;
}
void f() {
S s;
// Compare s with a record expression
bool b = s == (S) { a = 1, b = 2 };
}
Record expressions can be used in the right-hand side of assignments, in comparisons, in field selection expressions, and as arguments to functions, method or actions. Record expressions are not left values.
Record expressions that do not have ... as their last element must provide
a value for every member of the struct or header type to which it evaluates, by
mentioning each field name exactly once.
Record expressions that have ... as their last element are allowed to give
values to only a subset of the fields of the struct or header type to which it
evaluates. Any field names not given a value explicitly will be given their
default values (see [sec-initializing-with-default-values]).
The order of the fields of the struct or header type does not need to match
the order of the values of the structure-valued expression.
It is a compile-time error if a field name appears more than once in the same structure-valued expression.
8.6.4.2. Well-formedness
Click to view the specification source
rulegroup Type_wf/recordTypeIR:
rule Type_wf/non-default:
B |- RECORD `{(_ typeIR_field nameIR_field ;)*}
-- if $distinct_<nameIR>(nameIR_field*)
-- (Type_wf: B |- typeIR_field)*
rule Type_wf/default:
B |- RECORD `{(_ typeIR_field nameIR_field ;)* , ...}
-- if $distinct_<nameIR>(nameIR_field*)
-- (Type_wf: B |- typeIR_field)*
8.6.5. Sets
setTypeIR
: SET `< typeIR >
;
The set type 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 [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.
Set values are represented as:
setValue
: SET `{ value }
| SET `{ value* `( nat ) }
| SET `{ value &&& value }
| SET `{ value .. value }
| SET `{ ... }
;
8.6.5.1. Operations
Some P4 expressions denote sets of values. These expressions can appear only in
a few contexts—parsers and table entries. For example, the select expression
([sec-select]) has the following structure:
select (expression) {
set1: state1;
set2: state2;
// More labels omitted
}
Here the expressions set1, set2, etc. evaluate to sets of values and the
select expression tests whether expression belongs to the sets used as
labels.
keysetExpression
: simpleKeysetExpression
| tupleKeysetExpression
;
tupleKeysetExpression
: `( expression &&& expression )
| `( expression .. expression )
| `( DEFAULT )
| `( _ )
| `( simpleKeysetExpression , simpleKeysetExpressionList )
;
simpleKeysetExpressionList
: simpleKeysetExpression
| simpleKeysetExpressionList , simpleKeysetExpression
;
simpleKeysetExpression
: expression
| expression &&& expression
| expression .. expression
| DEFAULT
| _
;
The mask (&&&) and range (..) operators have the same precedence; the just
above the ?: operator.
8.6.5.1.1. Singleton sets
In a set context, expressions denote singleton sets. For example, in the following program fragment,
select (hdr.ipv4.version) {
4: continue;
}
The label 4 denotes the singleton set containing the int value 4.
8.6.5.1.2. The universal set
In a set context, the expressions default or _ denote the universal set,
which contains all possible values of a given type:
select (hdr.ipv4.version) {
4: continue;
_: reject;
}
8.6.5.1.3. Masks
The infix operator &&& takes two arguments of the same numeric type
([sec-numeric-values]), and creates a value of the same type. The right value
is used as a "mask", where each bit set to 0 in the mask indicates a "don’t
care" bit. More formally, the set denoted by a &&& b is defined as follows:
a &&& b = { c where a & b = c & b }
For example:
8w0x0A &&& 8w0x0F
denotes a set that contains 16 different bit<8> values, whose bit-pattern is
XXXX1010, where the value of an X can be any bit. Note that there may be
multiple ways to express a keyset using a mask operator—e.g., 8w0xFA &&&
8w0x0F denotes the same keyset as in the example above.
Similar to other binary operations, the mask operator allows the compiler to automatically insert casts to unify the argument types in certain situations (Section 17.2).
P4 architectures may impose additional restrictions on the expressions on the left and right-hand side of a mask operator: for example, they may require that either or both sub-expressions be compile-time known values.
8.6.5.1.4. Ranges
The infix operator .. takes two arguments of the same numeric type T
([sec-numeric-values]), and creates a value of the type set<T>. The set
contains all values numerically between the first and the second, inclusively.
For example:
4s5 .. 4s8
denotes a set of 4 consecutive int<4> values 4s5, 4s6, 4s7, and 4s8.
Similar to other binary operations, the range operator allows the compiler to automatically insert casts to unify the argument types in certain situations ([sec-implicit-casts]).
A range where the second value is smaller than the first one represents an empty set.
8.6.5.1.5. Products
Multiple sets can be combined using Cartesian product:
select(hdr.ipv4.ihl, hdr.ipv4.protocol) {
(4w0x5, 8w0x1): parse_icmp;
(4w0x5, 8w0x6): parse_tcp;
(4w0x5, 8w0x11): parse_udp;
(_, _): accept; }
The type of a product of sets is a set of sequences or tuples.
8.6.5.2. Well-formedness
Click to view the specification source
rulegroup Type_wf/setTypeIR: rule Type_wf/setTypeIR: B |- SET `<typeIR> -- if $nestable_set(typeIR) -- Type_wf: B |- typeIR
8.6.6. Table metadata
A table can be invoked by calling its apply method. Calling an apply method
on a table instance returns a value with a table metadata struct type with
three fields. This structure is synthesized by the compiler automatically. For
each table T, the compiler synthesizes a table metadata enum and a
struct, shown in pseudo-P4:
enum action_list(T) {
// one field for each action in the actions list of table T
}
struct apply_result(T) {
bool hit;
bool miss;
action_list(T) action_run;
}
8.6.6.1. Operations
The evaluation of the apply method sets the hit field to true and the
field miss to false if a match is found in the lookup-table; if a match is
not found hit is set to false and miss to true. These bits can be used
to drive the execution of the control-flow in the control block that invoked
the table:
if (ipv4_match.apply().hit) {
// there was a hit
} else {
// there was a miss
}
if (ipv4_host.apply().miss) {
ipv4_lpm.apply(); // Look up the route only if host table missed
}
The action_run field indicates which kind of action was executed
(irrespective of whether it was a hit or a miss). It can be used in a switch
statement:
switch (dmac.apply().action_run) {
Drop_action: { return; }
}
Internally, the type and value of table metadata are represented as:
tableMetadataTypeIR
: tableMetadataEnumTypeIR
| tableMetadataStructTypeIR
;
tableMetadataValue
: tableMetadataEnumValue
| tableMetadataStructValue
;
8.6.6.2. Well-formedness
Click to view the specification source
rulegroup Type_wf/tableMetadataTypeIR:
rule Type_wf/enum:
B |- TABLE_ENUM _ `{nameIR*}
-- if $distinct_<nameIR>(nameIR*)
rule Type_wf/struct:
B |- TABLE_STRUCT _ `{HIT _ ; MISS _ ; ACTION_RUN tableMetadataEnumTypeIR ;}
-- Type_wf: B |- tableMetadataEnumTypeIR
8.7. Unrolling
typedefs, described in Section 8.5.7, introduce indirection in types.
struct S {
bit<8> t;
int<8> u;
}
typedef S A;
The type A is an alias of the struct type S. Neverthelss, the
underlying type of A is a structTypeIR with two fields, t and u,
of types int<8> and bit<8> respectively. It is useful to be able to
work with the underlying type of A, when dealing with type checking, type
equivalence, and other type-based analyses.
Unrolling is the process of recursively expanding all typedefs until a type
is fully expanded to its underlying type.
Click to view the specification source
def $unroll_typeIR(TYPEDEF _ typeIR) = $unroll_typeIR(typeIR) def $unroll_typeIR(typeIR) = typeIR -- otherwise
9. P4 callables
P416 includes four entities that can be invoked: actions, functions, methods, and object constructors. Object constructors are explained in [sec-def-object]. The other three callable entities are collectively referred to as callables.
Callable types are created by the P4 compiler internally to represent the types of actions, functions, and methods during type-checking. We also call the type of a callable its signature. Libraries can contain callable declarations.
For example, consider the following declarations:
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;
}
These declarations describe two callables:
-
random, which has an extern 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
-
-
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>
-
Specifically, during type checking, callable declarations introduce a type constructor for the callable type:
callableTypeDefIR
: actionTypeDefIR
| functionTypeDefIR
| methodTypeDefIR
;
These callable type definitions are used to construct callable types via specialization:
callableTypeIR
: actionTypeIR
| functionTypeIR
| methodTypeIR
;
The well-formedness rules for callable types are checked by:
The runtime representation of a callable is given by:
callableDef
: actionDef
| functionDef
| methodDef
;
9.1. Parameters
direction
: /* empty */
| IN
| OUT
| INOUT
;
parameter
: annotationList direction type name initializerOpt
;
Callables can have parameters. Each parameter has a direction, a type, a name, and an optional default value.
A parameter is internally represented as:
parameterIR
: annotationList direction typeIR id parameterInitializerOptIR
;
The high-level overview of parameters with respect to the copy in/copy out calling convention is described in Section 6.5. Below are the formal typing and well-formedness rules for parameters that implement the restrictions described there.
9.1.1. Type checking
Typing a parameter produces a parameterIR if it is well-typed. A parameter
is well-typed when its type is well-formed and its default value (if any) has
the same type as the parameter type.
Click to view the specification source
rulegroup Parameter_ok:
rule Parameter_ok/non-initializer:
p TC_0 |- annotationList direction type name `EMPTY : TC_1 parameterIR # typeId_fresh*
-- Type_ok: p TC_0 |- type : typeIR # typeId_fresh*
-- if B = $union_set<typeId>($bound(p, TC_0), `{typeId_fresh*})
-- Type_wf: B |- typeIR
-- if nameIR = $name(name)
-- if parameterIR = annotationList direction typeIR nameIR eps
-- if TC_1 = $add_parameter_t(p, TC_0, parameterIR)
rule Parameter_ok/initializer-ctk:
p TC_0 |- annotationList direction type name (= expression_init) : TC_1 parameterIR # typeId_fresh*
-- Type_ok: p TC_0 |- type : typeIR # typeId_fresh*
-- if B = $union_set<typeId>($bound(p, TC_0), `{typeId_fresh*})
-- Type_wf: B |- typeIR
-- Expr_ok: p TC_0 |- expression_init : typedExpressionIR_init
-- if typeIR_init = $type_of_typedExpressionIR(typedExpressionIR_init)
-- if CTK = $ctk_of_typedExpressionIR(typedExpressionIR_init)
-- if typedExpressionIR_init_cast = $cast_unary(typedExpressionIR_init, typeIR)
-- if nameIR = $name(name)
-- if parameterInitializerIR = = typedExpressionIR_init_cast
-- if parameterIR = annotationList direction typeIR nameIR parameterInitializerIR
-- if TC_1 = $add_parameter_t(p, TC_0, parameterIR)
rule Parameter_ok/initializer-lctk:
p TC_0 |- annotationList direction type name (= expression_init) : TC_1 parameterIR # typeId_fresh*
-- Type_ok: p TC_0 |- type : typeIR # typeId_fresh*
-- if B = $union_set<typeId>($bound(p, TC_0), `{typeId_fresh*})
-- Type_wf: B |- typeIR
-- Expr_ok: p TC_0 |- expression_init : typedExpressionIR_init
-- if typeIR_init = $type_of_typedExpressionIR(typedExpressionIR_init)
-- if LCTK = $ctk_of_typedExpressionIR(typedExpressionIR_init)
-- if typedExpressionIR_init_cast = $cast_unary(typedExpressionIR_init, typeIR)
-- if nameIR = $name(name)
-- Expr_eval_lctk: p TC_0 |- typedExpressionIR_init_cast ~> value_init
-- if parameterInitializerIR = = `VALUE value_init
-- if parameterIR = annotationList direction typeIR nameIR parameterInitializerIR
-- if TC_1 = $add_parameter_t(p, TC_0, parameterIR)
9.1.2. Well-formedness
A parameter type is well-formed when its type is well-formed. Also, the type
should not be a synthesized type. Types that can only be local compile-time
known or compile-time known (e.g., int type) must be directionless. A default
value can be specified for in or directionless parameters only.
Click to view the specification source
rulegroup ParameterType_wf: rule ParameterType_wf/non-default: B |- _ direction typeIR _ eps -- Type_wf: B |- typeIR -- if typeIR_unroll = $unroll_typeIR(typeIR) -- if ~(typeIR_unroll <: synthesizedTypeIR) -- if ((typeIR_unroll = INT) \/ (typeIR_unroll <: stringTypeIR) \/ (typeIR_unroll <: objectTypeIR)) => direction = `EMPTY rule ParameterType_wf/default: B |- annotationList direction typeIR _ parameterInitializerIR -- Type_wf: B |- typeIR -- if typeIR_unroll = $unroll_typeIR(typeIR) -- if ~(typeIR_unroll <: synthesizedTypeIR) -- if ((typeIR_unroll = INT) \/ (typeIR_unroll <: stringTypeIR) \/ (typeIR_unroll <: objectTypeIR)) => direction = `EMPTY -- if direction = IN \/ direction = `EMPTY -- if ~$optional_annotation_of_parameterIR'(annotationList)
9.2. Callable definitions (callable constructors)
Similar to how type declarations introduce type definitions (see Section 8.3.1), callable declarations introduce callable definitions. These are constructors for callable types, that are parameterized by type parameters (if any).
actionTypeDefIR = actionTypeIR
functionTypeDefIR
: definedFunctionTypeDefIR
| externFunctionTypeDefIR
;
methodTypeDefIR
: externMethodTypeDefIR
| parserApplyMethodTypeDefIR
| controlApplyMethodTypeDefIR
| tableApplyMethodTypeDefIR
;
callableTypeDefIR
: actionTypeDefIR
| functionTypeDefIR
| methodTypeDefIR
;
When specialized with concrete type arguments, callable type definitions yield callable types:
actionTypeIR
: annotationList ACTION nameIR `( parameterIR* )
;
functionTypeIR
: definedFunctionTypeIR
| externFunctionTypeIR
;
methodTypeIR
: builtinMethodTypeIR
| externMethodTypeIR
| parserApplyMethodTypeIR
| controlApplyMethodTypeIR
| tableApplyMethodTypeIR
;
callableTypeIR
: actionTypeIR
| functionTypeIR
| methodTypeIR
;
For example, a function declaration introduces a function type definition:
T identity<T>(in T t) { return t; }
identity is a generic function type definition that falls into:
definedFunctionTypeDefIR
: FUNCTION nameIR `< typeParameterIR* , typeParameterIR* > `( parameterIR* )
: typeIR
;
Notice that definedFunctionTypeDefIR does not include the function body.
Because callable type definitions are used during type checking, the body of
the callable is not part of the type definition. After specialization with
proper type argument(s), it yields a function type:
definedFunctionTypeIR
: FUNCTION nameIR `( parameterIR* ) : typeIR
;
9.2.1. Well-formedness
${relation: CallableTypeDef_wf}
${rulegroup: CallableTypeDef_wf} ${ruleprose: CallableTypeDef_wf}
9.3. Actions
Actions are code fragments that can read and write the data being processed.
Actions may contain data values that can be written by the control plane and
read by the data plane. Actions are the main construct by which the control
plane can dynamically influence the behavior of the data plane. Figure 10
shows the abstract model of an action.
actionDeclaration
: annotationList ACTION name `( parameterList ) blockStatement
;
Syntactically actions resemble functions with no return value. Actions may be declared within a control block; in this case they can only be used within instances of that control block.
The following example shows an action declaration:
action Forward_a(out bit<9> outputPort, bit<9> port) {
outputPort = port;
}
Action parameters may not have extern types. Action parameters that have no
direction (e.g., port in the previous example) indicate "action data." All
such parameters must appear at the end of the parameter list. When used in a
match-action table (see [sec-table-action-list]), these parameters will be
provided by the table entries (e.g., as specified by the control plane, the
default_action table property, or the entries table property).
The body of an action consists of a sequence of statements and declarations.
No table, control, or parser applications can appear within actions.
Some targets may impose additional restrictions on action bodies—e.g., only allowing straight-line code, with no conditional statements or expressions.
Actions can be executed in two ways:
-
Implicitly: by tables during match-action processing.
-
Explicitly: either from a
controlblock or from anotheraction. In either case, the values for all action parameters must be supplied explicitly, including values for the directionless parameters. In this case, the directionless parameters behave likeinparameters.
Actions are introduced by action declarations. Its typing and instantiation
rules are explained in Section 11.6. It introduces an action type
definition:
actionTypeDefIR = actionTypeIR
Because actions cannot be generic, the action type is the same as the action type definition:
actionTypeIR
: annotationList ACTION nameIR `( parameterIR* )
;
The runtime representation of an action is as follows:
actionDef
: ACTION nameIR `( parameterListIR ) blockStatementIR
;
9.3.1. Well-formedness
Click to view the specification source
rulegroup CallableType_wf/actionTypeIR: rule CallableType_wf/actionTypeIR: B |- _ ACTION _ `(parameterIR*) -- (ParameterType_wf: B |- parameterIR)* -- (if (~$is_optional_parameterIR(parameterIR)))* -- (if (_ direction typeIR _ _ = parameterIR))* -- if $directionless_trailing(direction*) -- (if $nestable_action(typeIR))*
9.4. Functions
Two kinds of functions are supported in P4: user-defined functions and extern functions. The former are functions whose implementation is provided in P4 source code, while the latter are functions whose implementation is provided externally by the target architecture.
The internal representation of functions is as follows:
functionTypeDefIR
: definedFunctionTypeDefIR
| externFunctionTypeDefIR
;
functionTypeIR
: definedFunctionTypeIR
| externFunctionTypeIR
;
functionDef
: definedFunctionDef
| externFunctionDef
;
9.4.1. User-defined functions
Functions can only be declared at the top level and all parameters must have a direction. P4 functions are modeled after functions as found in most other programming languages, but the language does not permit recursive functions.
functionPrototype
: typeOrVoid name typeParameterListOpt `( parameterList )
;
functionDeclaration
: annotationList functionPrototype blockStatement
;
Here is an example of a function that returns the maximum of two 32-bit values:
bit<32> max(in bit<32> left, in bit<32> right) {
return (left > right) ? left : right;
}
A function returns a value using the return statement. A function with a
return type of void can simply use the return statement with no arguments.
A function with a non-void return type must return a value of the suitable type
on all possible execution paths.
The typing and instantiation rules for function declarations is explained in
Section 11.5. This introduces a function type definition, which
can be specialized to a function type.
definedFunctionTypeDefIR
: FUNCTION nameIR `< typeParameterIR* , typeParameterIR* > `( parameterIR* )
: typeIR
;
definedFunctionTypeIR
: FUNCTION nameIR `( parameterIR* ) : typeIR
;
Also, the runtime representation of a defined function is as follows:
definedFunctionDef
: FUNCTION nameIR `< typeParameterListIR > `( parameterListIR )
blockStatementIR
;
9.4.1.1. Well-formedness
Click to view the specification source
rulegroup CallableType_wf/definedFunctionTypeIR: rule CallableType_wf/definedFunctionTypeIR: B |- FUNCTION _ `(parameterIR*) : typeIR_ret -- (ParameterType_wf: B |- parameterIR)* -- (if (~$is_optional_parameterIR(parameterIR)))* -- (if (_ _ typeIR _ _ = parameterIR))* -- (if $nestable_definedFunction(typeIR))* -- ReturnType_wf: B |- typeIR_ret
9.4.2. Extern functions
An extern function declaration describes the name and type signature of the function, but not its implementation.
externDeclaration
: externFunctionDeclaration
| externObjectDeclaration
;
extern declarations introduce extern function type definitions, which can be
specialized to extern function types.
externFunctionTypeDefIR
: EXTERN_FUNCTION nameIR `< typeParameterIR* , typeParameterIR* >
`( parameterIR* ) : typeIR
;
externFunctionTypeIR
: EXTERN_FUNCTION nameIR `( parameterIR* ) : typeIR
;
Also, the runtime representation of an extern function is as follows:
externFunctionDef
: EXTERN_FUNCTION nameIR `< typeParameterListIR > `( parameterListIR )
;
See Section 11.9 for the typing and instantiation
rules for extern function declarations.
9.4.2.1. Well-formedness
Click to view the specification source
rulegroup CallableType_wf/externFunctionTypeIR: rule CallableType_wf/externFunctionTypeIR: B |- EXTERN_FUNCTION _ `(parameterIR*) : typeIR_ret -- (ParameterType_wf: B |- parameterIR)* -- (if (_ _ typeIR _ _ = parameterIR))* -- (if $nestable_externFunction(typeIR))* -- ReturnType_wf: B |- typeIR_ret
9.5. Methods
Objects in P4 can have methods associated with them. Specifically, extern
objects have methods declared in their definitions. parser, control, and
table objects have built-in apply methods.
Further, P4 allows built-in methods for certain types, such as isValid()
and setValid() methods for header types.
The internal representation of methods is defined as follows:
methodTypeDefIR
: externMethodTypeDefIR
| parserApplyMethodTypeDefIR
| controlApplyMethodTypeDefIR
| tableApplyMethodTypeDefIR
;
methodTypeIR
: builtinMethodTypeIR
| externMethodTypeIR
| parserApplyMethodTypeIR
| controlApplyMethodTypeIR
| tableApplyMethodTypeIR
;
methodDef
: externMethodDef
| parserApplyMethodDef
| controlApplyMethodDef
| tableApplyMethodDef
;
9.5.1. Extern methods
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.
externConstructorPrototype
: annotationList typeIdentifier `( parameterList ) ;
;
externMethodPrototype
: annotationList functionPrototype ;
| annotationList ABSTRACT functionPrototype ;
;
externConstructorOrMethodPrototype
: externConstructorPrototype
| externMethodPrototype
;
externObjectDeclaration
: annotationList EXTERN nonTypeName typeParameterListOpt
`{ externConstructorOrMethodPrototypeList }
;
For example, the P4 core library introduces two extern objects packet_in
and packet_out used for manipulating packets (see
[sec-packet-data-extraction] and [sec-deparse]). Here is an example
showing how the methods of these objects can be invoked on a packet:
extern packet_out {
void emit<T>(in T hdr);
}
control d(packet_out b, in Hdr h) {
apply {
b.emit(h.ipv4); // write ipv4 header into output packet
} // by calling emit method
}
Internally, extern methods are represented as follows:
externMethodTypeDefIR
: EXTERN_METHOD nameIR `< typeParameterIR* , typeParameterIR* >
`( parameterIR* ) : typeIR
| EXTERN_METHOD ABSTRACT nameIR `< typeParameterIR* , typeParameterIR* >
`( parameterIR* ) : typeIR
;
externMethodTypeIR
: EXTERN_METHOD nameIR `( parameterIR* ) : typeIR
| EXTERN_METHOD ABSTRACT nameIR `( parameterIR* ) : typeIR
;
externMethodDef
: EXTERN_METHOD nameIR `< typeParameterListIR > `( parameterListIR )
blockStatementIR?
| EXTERN_METHOD ABSTRACT nameIR `< typeParameterListIR > `( parameterListIR )
;
Typical extern object methods are implemented by the target architecture. P4 programmers can only call such methods.
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:
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);
}
When such an object is instantiated the user has to supply an implementation
of all the abstract methods (see [sec-instantion-abstract-method]).
9.5.1.1. Well-formedness
Click to view the specification source
rulegroup CallableType_wf/externMethodTypeIR: rule CallableType_wf/non-abstract: B |- EXTERN_METHOD _ `(parameterIR*) : typeIR_ret -- (ParameterType_wf: B |- parameterIR)* -- (if (_ _ typeIR _ _ = parameterIR))* -- (if $nestable_externMethod(typeIR))* -- ReturnType_wf: B |- typeIR_ret rule CallableType_wf/abstract: B |- EXTERN_METHOD ABSTRACT _ `(parameterIR*) : typeIR_ret -- (ParameterType_wf: B |- parameterIR)* -- (if (~$is_optional_parameterIR(parameterIR)))* -- (if (_ _ typeIR _ _ = parameterIR))* -- (if $nestable_externMethod(typeIR))* -- ReturnType_wf: B |- typeIR_ret
9.5.2. Parser apply methods
P4 allows parsers to invoke the services of other parsers, similar to
subroutines. To invoke the services of another parser, the sub-parser must be
first instantiated; the services of an instance are invoked by calling it using
its apply method.
The following example shows a sub-parser invocation:
parser callee(packet_in packet, out IPv4 ipv4) { /* body omitted */ }
parser caller(packet_in packet, out Headers h) {
callee() subparser; // instance of callee
state subroutine {
subparser.apply(packet, h.ipv4); // invoke sub-parser
transition accept; // accept if sub-parser ends in accept state
}
}
A parser apply method type definition is implicitly introduced by a parser
declaration. Its semantics is explained in Section 11.11. A parser
apply method is represented internally as follows:
parserApplyMethodTypeDefIR = parserApplyMethodTypeIR
parserApplyMethodTypeIR
: PARSER_APPLY `( parameterIR* )
;
parserApplyMethodDef
: PARSER_APPLY `( parameterListIR )
`{ parserLocalDeclarationListIR ; stateEnv }
;
9.5.2.1. Well-formedness
Click to view the specification source
rulegroup CallableType_wf/parserApplyMethodTypeIR: rule CallableType_wf/parserApplyMethodTypeIR: B |- PARSER_APPLY `(parameterIR*) -- (ParameterType_wf: B |- parameterIR)* -- (if (~$is_optional_parameterIR(parameterIR)))* -- (if (_ _ typeIR _ _ = parameterIR))* -- (if $nestable_parserApplyMethod(typeIR))*
9.5.3. Control apply methods
P4 allows controls to invoke the services of other controls, similar to
subroutines. To invoke the services of another control, it must be first
instantiated; the services of an instance are invoked by calling it using its
apply method.
The following example shows a control invocation:
control Callee(inout IPv4 ipv4) { /* body omitted */ }
control Caller(inout Headers h) {
Callee() instance; // instance of callee
apply {
instance.apply(h.ipv4); // invoke control
}
}
A control apply method type definition is implicitly introduced by a
control declaration. Its semantics is explained in
Section 11.12. A control apply method is represented internally
as follows:
controlApplyMethodTypeDefIR = controlApplyMethodTypeIR
controlApplyMethodTypeIR
: CONTROL_APPLY `( parameterIR* )
;
controlApplyMethodDef
: CONTROL_APPLY `( parameterListIR )
`{ controlLocalDeclarationListIR ; actionDefEnv ; controlBodyIR }
;
9.5.3.1. Well-formedness
Click to view the specification source
rulegroup CallableType_wf/controlApplyMethodTypeIR: rule CallableType_wf/controlApplyMethodTypeIR: B |- CONTROL_APPLY `(parameterIR*) -- (ParameterType_wf: B |- parameterIR)* -- (if (~$is_optional_parameterIR(parameterIR)))* -- (if (_ _ typeIR _ _ = parameterIR))* -- (if $nestable_controlApplyMethod(typeIR))*
9.5.4. Table apply methods
A table can be invoked by calling its apply method. Calling an apply method
on a table instance returns a synthesized table metadata structure, explained
in Section 8.6.6.
A table apply method definition is introduced by a table declaration. Its
semantics rule is explained in [sec-table-declaration]. A table declaration
may not be generic. Thus, the table apply method type definition does not have
type parameters, and is same as the table apply method type. A table apply
method is represented internally as follows:
tableApplyMethodTypeDefIR = tableApplyMethodTypeIR
tableApplyMethodTypeIR
: TABLE_APPLY : tableMetadataStructTypeIR
;
tableApplyMethodDef
: TABLE_APPLY `{ tablePropertyListIR }
;
9.5.4.1. Well-formedness
Click to view the specification source
rulegroup CallableType_wf/tableApplyMethodTypeIR: rule CallableType_wf/tableApplyMethodTypeIR: B |- TABLE_APPLY : tableMetadataStructTypeIR
9.5.5. Built-in methods
Built-in methods are methods associated with certain types defined by the P4 language. Thus, they do not have method type definitions, and only have method types. The internal representations are:
builtinMethodTypeIR
: BUILTIN_METHOD nameIR `( parameterIR* ) : typeIR
;
10. P4 objects and constructors
10.1. Objects
tables, value_sets, and extern objects are stateful entities in P4
programs. These objects should be instantiated statically prior to program
execution. parsers, controls, and packages may contain stateful
objects, thus they must also be instantiated.
The runtime representation of these objects is defined as follows:
object
: externObject
| parserObject
| controlObject
| packageObject
| tableObject
| valueSetObject
;
The instantiation phase (described in Section 7.3) creates objects in the source P4 program, and allocates them in the global store. At runtime (described in Section 7.4), these objects interact, as defined by the target architecture, to process packets.
10.1.1. Fully-qualified names
Instantiation may create multiple instances of a type, each of which must
have a unique, fully-qualified name. Thus, objects are identified by their
full-qualified names (objectId). Section 7.3.1 describes
how a fully-qualified name is constructed.
objectId = nameIR*
10.2. Constructors
Objects can be instantiated in three ways:
-
Using instantiations, described in Section 11.4.
-
Using constructor call expressions, described in [sec-constructor-call-expression].
-
Using direct type invocation statements, described in Section 13.5.
Instantiations and constructor call expressions invoke constructors, which
creates an object as a result. Direct application statements are syntactic
sugar for instantiations of parser and control objects.
Constructor invocation is similar to callable invocation; constructors can also be overloaded, and called using named arguments.
The following example shows a constructor invocation for setting the target-dependent implementation property of a table:
extern ActionProfile {
ActionProfile(bit<32> size); // constructor
}
table tbl {
actions = { /* body omitted */ }
implementation = ActionProfile(1024); // constructor invocation
}
10.2.1. Constructor parameters
In order to support libraries of useful P4 components, externs, parsers,
controls, and packages can additionally be parameterized through the
use of constructor parameters.
Consider the parser declaration syntax:
parserDeclaration
: annotationList PARSER name typeParameterListOpt `( parameterList )
constructorParameterListOpt
`{ parserLocalDeclarationList parserStateList }
;
constructorParameterListOpt
: /* 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 (
constructorParameterListOpt)
Because constructors are evaluated entirely at compilation time, all
constructor arguments must be expressions that can be evaluated at compilation
time. In consequence, constructor parameters must be directionless
(i.e., they cannot be in, out, or inout).
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;
Internally, constructor parameters are represented as:
constructorParameterIR = parameterIR
parameterIR
: annotationList direction typeIR id parameterInitializerOptIR
;
10.2.1.1. Well-formedness
Click to view the specification source
rulegroup ConstructorParameterType_wf: rule ConstructorParameterType_wf: B |- constructorParameterIR -- if _ `EMPTY _ _ _ = constructorParameterIR -- ParameterType_wf: B |- constructorParameterIR
10.2.2. Constructor definitions
Similar to how type declarations introduce type definitions and callable declarations introduce callable definitions (see Section 9.2), constructor declarations introduce constructor definitions. These are parameterized by type parameters, and produces a constructor type when specialized with specific type arguments.
The runtime representation of constructors is defined as follows:
constructorDef
: externObjectConstructorDef
| parserObjectConstructorDef
| controlObjectConstructorDef
| packageObjectConstructorDef
;
When type arguments and constructor arguments are supplied to a constructor definition, an object of the constructed type is created.
Similar to how callable types are internally used during type checking to represent callables, constructor types internally represent the types of constructors.
constructorTypeIR
: CONSTRUCTOR `( constructorParameterIR* ) : typeIR
;
constructorTypeDefIR
: CONSTRUCTOR `< typeParameterIR* , typeParameterIR* >
`( constructorParameterIR* ) : typeIR
;
10.2.2.1. Well-formedness
Click to view the specification source
rulegroup ConstructorTypeDef_wf:
rule ConstructorTypeDef_wf:
B |- constructorTypeDefIR
-- if CONSTRUCTOR `<typeParameterIR_expl* , typeParameterIR_impl*> `(parameterIR*) : typeIR_object = constructorTypeDefIR
-- if $definable_constructor(typeIR_object)
-- if typeParameterIR* = typeParameterIR_expl* ++ typeParameterIR_impl*
-- if $distinct_<typeId>(typeParameterIR*)
-- if B_inner = $union_set<typeId>(B, `{typeParameterIR*})
-- ConstructorType_wf: B_inner |- CONSTRUCTOR `(parameterIR*) : typeIR_object
10.3. Extern objects
An extern object declaration introduces an extern object type and its
associated methods. 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.
externConstructorPrototype
: annotationList typeIdentifier `( parameterList ) ;
;
externMethodPrototype
: annotationList functionPrototype ;
| annotationList ABSTRACT functionPrototype ;
;
externObjectDeclaration
: annotationList EXTERN nonTypeName typeParameterListOpt
`{ externConstructorOrMethodPrototypeList }
;
Internally, an extern object constructor is represented as:
externObjectConstructorDef
: EXTERN nameIR `< typeParameterListIR > `( constructorParameterListIR )
`{ externMethodPrototypeListIR }
;
externMethodPrototypeIR
: annotationList functionPrototypeIR ;
| annotationList ABSTRACT functionPrototypeIR ;
;
Extern objects are then instantiated by constructor invocations to yield:
externObject
: EXTERN typeId `< theta > `{ objectState frame externMethodDefEnv }
;
The only operation on extern objects is invoking a method of an extern object
instance using a method call expression.
Controls, parsers, packages, and extern constructors can have parameters of
type extern only if they are directionless parameters. Extern object
instances can thus be used as arguments in the construction of such objects.
No other operations are available on extern objects, e.g., equality comparison is not defined.
10.3.1. Well-formedness
Click to view the specification source
rulegroup ConstructorType_wf/externObjectTypeIR: rule ConstructorType_wf/externObjectTypeIR: B |- CONSTRUCTOR `(parameterIR*) : typeIR_object -- (ConstructorParameterType_wf: B |- parameterIR)* -- Type_wf: B |- typeIR_object -- if externObjectTypeIR = $unroll_typeIR(typeIR_object) -- (if (_ _ typeIR _ _ = parameterIR))* -- (if $nestable_constructor_extern(typeIR))*
10.3.2. Instantiation
Click to view the specification source
rulegroup Constructor_call/externConstructorDef:
rule Constructor_call/externConstructorDef:
p IC STO_0 |- externObjectConstructorDef `<typeArgumentIR*> `(argumentIR* # id_default* # id_optional*) : STO_2 object_extern
-- if EXTERN nameIR `<typeParameterIR*> `(constructorParameterIR*) `{externMethodPrototypeIR*} = externObjectConstructorDef
-- if p_callee = BLOCK
-- if IC_callee_0 = $inherit_i(GLOBAL, IC)
-- if theta = `{(typeParameterIR : typeArgumentIR)*}
-- if IC_callee_1 = IC_callee_0[BLOCK.TYPE = theta]
-- if GIVEN constructorParameterIR_aligned* DEFAULT constructorParameterIR_default* = $align_parameterListIR(constructorParameterIR*, argumentIR*, id_default*, id_optional*)
-- (if (_ _ _ id_aligned _ = constructorParameterIR_aligned))*
-- Copy_in_inst: STO_0 p IC argumentIR* @ p_callee IC_callee_1 id_aligned* ~> STO_1 IC_callee_2
-- Copy_in_inst_default: STO_0 p IC @ p_callee IC_callee_2 constructorParameterIR_default* ~> STO_2 IC_callee_3
-- ExternMethods_inst: IC_callee_3 |- externMethodPrototypeIR* : IC_callee_4
-- if `{(callableId : externMethodDef)*} = IC_callee_4.BLOCK.CALLABLE
-- (if (_ _ _ id_cparam _ = constructorParameterIR))*
-- (if (value_cparam = $find_map<id, value>(IC_callee_4.BLOCK.FRAME, id_cparam)))*
-- if objectState = $init_objectState(nameIR, typeArgumentIR*, value_cparam*)
-- if theta_extern = IC_callee_4.BLOCK.TYPE
-- if frame_extern = IC_callee_4.BLOCK.FRAME
-- if externMethodDefEnv_extern = `{(callableId : externMethodDef)*}
-- if object_extern = EXTERN nameIR `<theta_extern> `{objectState frame_extern externMethodDefEnv_extern}
10.4. Parser objects
A parser declaration comprises a name, a list of parameters, an optional list of constructor parameters, local elements, and parser states (as well as optional annotations).
parserDeclaration
: annotationList PARSER name typeParameterListOpt `( parameterList )
constructorParameterListOpt
`{ parserLocalDeclarationList parserStateList }
;
parserLocalDeclaration
: constantDeclaration
| instantiation
| variableDeclaration
| valueSetDeclaration
;
parserState
: annotationList STATE name `{ parserStatementList transitionStatement }
;
At least one state, named start, must be present in any parser. A parser
may not define two states with the same name. It is also illegal for a parser
to give explicit definitions for the accept and reject states—those
states are logically distinct from the states defined by the programmer.
Preceding the parser states, a parser may also contain a list of local
elements. These can be constants, variables, or instantiations of objects that
may be used within the parser. Such objects may be instantiations of extern
objects, or other parsers that may be invoked as subroutines. However, it
is illegal to instantiate a control block within a parser.
The states and local elements are all in the same namespace, thus the following example will produce an error:
// erroneous example
parser p() {
bit<4> t;
state start {
t = 1;
transition t;
}
state t { // error: name t is duplicated
transition accept;
}
}
For an example containing a complete declaration of a parser see Section 5.3.
A parser declaration introduces a parser object constructor, represented
as:
parserObjectConstructorDef
: PARSER `< typeParameterListIR > `( parameterListIR )
`( constructorParameterListIR )
`{ parserLocalDeclarationListIR parserStateListIR }
;
Parser objects are then instantiated by constructor invocations to yield:
parserObject
: PARSER `< theta > `( parameterListIR )
`{ frame parserLocalDeclarationListIR stateEnv }
;
10.4.1. Well-formedness
Click to view the specification source
rulegroup ConstructorType_wf/parserObjectTypeIR: rule ConstructorType_wf/parserObjectTypeIR: B |- CONSTRUCTOR `(parameterIR*) : typeIR_object -- (ConstructorParameterType_wf: B |- parameterIR)* -- (if (~$is_optional_parameterIR(parameterIR)))* -- Type_wf: B |- typeIR_object -- if parserObjectTypeIR = $unroll_typeIR(typeIR_object) -- (if (_ _ typeIR _ _ = parameterIR))* -- (if $nestable_constructor_parser(typeIR))*
10.4.2. Instantiation
Click to view the specification source
rulegroup Constructor_call/parserConstructorDef:
rule Constructor_call/parserConstructorDef:
p IC STO_0 |- parserObjectConstructorDef `<typeArgumentIR*> `(argumentIR* # id_default* # id_optional*) : STO_4 object_parser
-- if PARSER `<typeParameterIR*> `(parameterIR*) `(constructorParameterIR*) `{parserLocalDeclarationIR* parserStateIR*} = parserObjectConstructorDef
-- if p_callee = BLOCK
-- if IC_callee_0 = $inherit_i(GLOBAL, IC)
-- if theta_parser = `{(typeParameterIR : typeArgumentIR)*}
-- if IC_callee_1 = IC_callee_0[BLOCK.TYPE = theta_parser]
-- if GIVEN constructorParameterIR_aligned* DEFAULT constructorParameterIR_default* = $align_parameterListIR(constructorParameterIR*, argumentIR*, id_default*, id_optional*)
-- (if (_ _ _ id_aligned _ = constructorParameterIR_aligned))*
-- Copy_in_inst: STO_0 p IC argumentIR* @ p_callee IC_callee_1 id_aligned* ~> STO_1 IC_callee_2
-- Copy_in_inst_default: STO_0 p IC @ p_callee IC_callee_2 constructorParameterIR_default* ~> STO_2 IC_callee_3
-- ParserLocalDecls_inst: IC_callee_3 STO_2 |- parserLocalDeclarationIR* : IC_local STO_3 parserLocalDeclarationIR_inst*
-- ParserStates_inst: IC_local STO_3 |- parserStateIR* : IC_state STO_4
-- if frame_parser = IC_callee_3.BLOCK.FRAME
-- if stateEnv_parser = IC_state.BLOCK.STATE
-- if object_parser = PARSER `<theta_parser> `(parameterIR*) `{frame_parser parserLocalDeclarationIR_inst* stateEnv_parser}
10.5. Control objects
P4 parsers are responsible for extracting bits from a packet into headers.
These headers (and other metadata) can be manipulated and transformed within
control blocks. The body of a control block resembles a traditional
imperative program. Within the body of a control block, match-action units can
be invoked to perform data transformations. Match-action units are represented
in P4 by constructs called tables.
Syntactically, a control block is declared with a name, parameters, optional
type parameters, and a sequence of declarations of constants, variables,
actions, tables, and other instantiations:
controlDeclaration
: annotationList CONTROL name typeParameterListOpt `( parameterList )
constructorParameterListOpt
`{ controlLocalDeclarationList APPLY controlBody }
;
controlLocalDeclaration
: constantDeclaration
| instantiation
| variableDeclaration
| actionDeclaration
| tableDeclaration
;
controlBody = blockStatement
It is illegal to instantiate a parser within a control block.
P4 does not support exceptional control-flow within a control block. The only
statement which has a non-local effect on control flow is exit, which causes
execution of the enclosing control block to immediately terminate. That is,
there is no equivalent of the verify statement or the reject state from
parsers. Hence, all error handling must be performed explicitly by the
programmer.
A control declaration introduces a control object constructor, represented
as:
controlObjectConstructorDef
: CONTROL `< typeParameterListIR > `( parameterListIR )
`( constructorParameterListIR )
`{ controlLocalDeclarationListIR APPLY controlBodyIR }
;
Control objects are then instantiated by constructor invocations to yield:
controlObject
: CONTROL `< theta > `( parameterListIR )
`{ frame controlLocalDeclarationListIR actionDefEnv controlBodyIR }
;
10.5.1. Well-formedness
Click to view the specification source
rulegroup ConstructorType_wf/controlObjectTypeIR: rule ConstructorType_wf/controlObjectTypeIR: B |- CONSTRUCTOR `(parameterIR*) : typeIR_object -- (ConstructorParameterType_wf: B |- parameterIR)* -- (if (~$is_optional_parameterIR(parameterIR)))* -- Type_wf: B |- typeIR_object -- if controlObjectTypeIR = $unroll_typeIR(typeIR_object) -- (if (_ _ typeIR _ _ = parameterIR))* -- (if $nestable_constructor_control(typeIR))*
10.5.2. Instantiation
Click to view the specification source
rulegroup Constructor_call/controlConstructorDef:
rule Constructor_call/controlConstructorDef:
p IC STO_0 |- controlObjectConstructorDef `<typeArgumentIR*> `(argumentIR* # id_default* # id_optional*) : STO_4 object_control
-- if CONTROL `<typeParameterIR*> `(parameterIR*) `(constructorParameterIR*) `{controlLocalDeclarationIR* APPLY controlBodyIR} = controlObjectConstructorDef
-- if p_callee = BLOCK
-- if IC_callee_0 = $inherit_i(GLOBAL, IC)
-- if theta_control = `{(typeParameterIR : typeArgumentIR)*}
-- if IC_callee_1 = IC_callee_0[BLOCK.TYPE = theta_control]
-- if GIVEN constructorParameterIR_aligned* DEFAULT constructorParameterIR_default* = $align_parameterListIR(constructorParameterIR*, argumentIR*, id_default*, id_optional*)
-- (if (_ _ _ id_aligned _ = constructorParameterIR_aligned))*
-- Copy_in_inst: STO_0 p IC argumentIR* @ p_callee IC_callee_1 id_aligned* ~> STO_1 IC_callee_2
-- Copy_in_inst_default: STO_0 p IC @ p_callee IC_callee_2 constructorParameterIR_default* ~> STO_2 IC_callee_3
-- ControlLocalDecls_inst: IC_callee_3 STO_2 |- controlLocalDeclarationIR* : IC_local_0 STO_3 controlLocalDeclarationIR_inst*
-- Block_inst: IC_local_0 STO_3 |- controlBodyIR : IC_local_1 STO_4 controlBodyIR_inst
-- if frame_control = IC_callee_3.BLOCK.FRAME
-- if `{(callableId : actionDef)*} = IC_local_1.BLOCK.CALLABLE
-- if actionDefEnv_control = `{(callableId : actionDef)*}
-- if object_control = CONTROL `<theta_control> `(parameterIR*) `{frame_control controlLocalDeclarationIR_inst* `{(callableId : actionDef)*} controlBodyIR_inst}
10.6. Table objects
A table describes a match-action unit. The structure of a match-action unit
is shown in Figure 11. Processing a packet using a match-action table
executes the following steps:
-
Key construction.
-
Key lookup in a lookup table (the "match" step). The result of key lookup is an "action".
-
Action execution (the "action step") over the input data, resulting in mutations of the data.
A table declaration introduces a table instance. To obtain multiple instances
of a table, it must be declared within a control block that is itself
instantiated multiple times.
The look-up table is a finite map whose contents are manipulated asynchronously
(read/write) by the target control plane, through a separate control-plane API
(see Figure 11). Note that the term "table" is overloaded: it can
refer to the P4 table objects that appear in P4 programs, as well as the
internal look-up tables used in targets. We will use the term "match-action
unit" when necessary to disambiguate.
Syntactically, a table is defined in terms of a set of key-value properties. Some of these properties are "standard" properties, but the set of properties can be extended by target-specific compilers as needed. Note that duplicated properties are invalid and the compiler should reject them.
tableDeclaration
: annotationList TABLE name `{ tablePropertyList }
;
tablePropertyList
: /* empty */
| tablePropertyList tableProperty
;
tableProperty
: KEY = `{ tableKeyList }
| ACTIONS = `{ tableActionList }
| annotationList constOpt ENTRIES = `{ tableEntryList }
| annotationList constOpt tableCustomName initializer ;
;
See Section 16.3 for details on the table properties.
A table declaration creates a table object, thus table constructors are not
defined in P4IR. Table objects are defined as:
tableObject
: TABLE typeId `{ frame tableObjectProperty }
;
tablePropertyIR
: tableKeysPropertyIR
| tableActionsPropertyIR
| tableDefaultActionPropertyIR
| tableEntriesPropertyIR
| tableCustomPropertyIR
;
tableObjectProperty = {
KEYS tableKeysPropertyIR,
ACTIONS tableActionsPropertyIR,
DEFAULT_ACTION tableDefaultActionPropertyIR,
ENTRIES tableEntriesPropertyIR,
CUSTOMS tableCustomPropertyIR*
}
10.6.1. Well-formedness
Click to view the specification source
rulegroup ConstructorType_wf/tableObjectTypeIR: rule ConstructorType_wf/tableObjectTypeIR: B |- CONSTRUCTOR `(eps) : typeIR_object -- Type_wf: B |- typeIR_object -- if tableObjectTypeIR = $unroll_typeIR(typeIR_object)
10.7. Parser value set objects
In some cases, the values that determine the transition from one parser state to another need to be determined at runtime. MPLS is one example where the value of the MPLS label field is used to determine what headers follow the MPLS tag and this mapping may change dynamically at runtime. To support this functionality, P4 supports the notion of a Parser Value Set. This is a named set of values with a runtime API to add and remove values from the set.
Value sets are declared locally within a parser. They should be declared before
being referenced in parser keysetExpression and can be used as a label in a
select expression.
The syntax for declaring value sets is:
valueSetDeclaration
: annotationList VALUE_SET `< valueSetType > `( expression ) name ;
;
valueSetType
: baseType
| tupleType
| prefixedTypeName
;
Parser Value Sets support a size argument to provide hints to the compiler to
reserve hardware resources to implement the value set. Thus, the size
argument must be a compile-time known value. For example, this parser value
set:
value_set<bit<16>>(4) pvs;
creates a value_set of size 4 with entries of type bit<16>.
The semantics of the size argument is similar to the size property of a
table. If a value set has a size argument with value N, it is recommended
that a compiler should choose a data plane implementation that is capable of
storing N value set entries. See "Size property of P4 tables and parser value
sets"
[1]
for further discussion on the implementation of parser value set size.
The value set is populated by the control plane by methods specified in the P4Runtime specification.[2]
Similar to tables, a value_set declaration creates a new parser value set
object. Thus, no constructor is defined for them. The object is represented as:
valueSetObject
: VALUE_SET `{ value* `( nat ) }
;
10.8. Package objects
A package type declaration introduces a package object constructor.
packageTypeDeclaration
: annotationList PACKAGE name typeParameterListOpt `( parameterList ) ;
;
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.
Package object constructors are defined as follows:
packageObjectConstructorDef
: PACKAGE `< typeParameterListIR > `( constructorParameterListIR )
;
Package objects are then instantiated to yield:
packageObject
: PACKAGE `< theta > `{ frame }
;
10.8.1. Well-formedness
Click to view the specification source
rulegroup ConstructorType_wf/packageObjectTypeIR: rule ConstructorType_wf/packageObjectTypeIR: B |- CONSTRUCTOR `(parameterIR*) : typeIR_object -- (ConstructorParameterType_wf: B |- parameterIR)* -- Type_wf: B |- typeIR_object -- if packageObjectTypeIR = $unroll_typeIR(typeIR_object) -- (if (_ _ typeIR _ _ = parameterIR))* -- (if $nestable_constructor_package(typeIR))*
10.8.2. Instantiation
Click to view the specification source
rulegroup Constructor_call/packageConstructorDef:
rule Constructor_call/packageConstructorDef:
p IC STO_0 |- packageObjectConstructorDef `<typeArgumentIR*> `(argumentIR* # id_default* # id_optional*) : STO_2 object_package
-- if PACKAGE `<typeParameterIR*> `(constructorParameterIR*) = packageObjectConstructorDef
-- if p_callee = BLOCK
-- if IC_callee_0 = $inherit_i(GLOBAL, IC)
-- if IC_callee_1 = IC_callee_0[BLOCK.TYPE = `{(typeParameterIR : typeArgumentIR)*}]
-- if GIVEN constructorParameterIR_aligned* DEFAULT constructorParameterIR_default* = $align_parameterListIR(constructorParameterIR*, argumentIR*, id_default*, id_optional*)
-- (if (_ _ _ id_aligned _ = constructorParameterIR_aligned))*
-- Copy_in_inst: STO_0 p IC argumentIR* @ p_callee IC_callee_1 id_aligned* ~> STO_1 IC_callee_2
-- Copy_in_inst_default: STO_1 p IC @ p_callee IC_callee_2 constructorParameterIR_default* ~> STO_2 IC_callee_3
-- if theta_package = IC_callee_3.BLOCK.TYPE
-- if frame_package = IC_callee_3.BLOCK.FRAME
-- if object_package = PACKAGE `<theta_package> `{frame_package}
11. Declarations
The syntax of top-level declarations is defined as follows:
declaration
: constantDeclaration
| instantiation
| functionDeclaration
| actionDeclaration
| errorDeclaration
| matchKindDeclaration
| externDeclaration
| parserDeclaration
| controlDeclaration
| typeDeclaration
;
externDeclaration
: externFunctionDeclaration
| externObjectDeclaration
;
typeDeclaration
: derivedTypeDeclaration
| typedefDeclaration
| parserTypeDeclaration
| controlTypeDeclaration
| packageTypeDeclaration
;
As described in Chapter 7, declarations are type checked, instantiated at compile time, and evaluated at runtime.
11.1. Semantics of declarations
11.1.1. Type checking
Click to view the specification source
relation Decl_ok: typingContext |- declaration : typingContext declarationIR
After type checking, declarations are represented in P4IR as follows:
declarationIR
: constantDeclarationIR
| instantiationIR
| functionDeclarationIR
| actionDeclarationIR
| errorDeclarationIR
| matchKindDeclarationIR
| externDeclarationIR
| parserDeclarationIR
| controlDeclarationIR
| typeDeclarationIR
;
externDeclarationIR
: externFunctionDeclarationIR
| externObjectDeclarationIR
;
typeDeclarationIR
: derivedTypeDeclarationIR
| typedefDeclarationIR
| parserTypeDeclarationIR
| controlTypeDeclarationIR
| packageTypeDeclarationIR
;
A list of declarations is type checked as follows:
Click to view the specification source
relation Decls_ok: typingContext |- declaration* : typingContext declarationIR*
Click to view the specification source
rulegroup Decls_ok: rule Decls_ok/nil: TC |- eps : TC eps rule Decls_ok/cons: TC_0 |- declaration_h :: declaration_t* : TC_2 (declarationIR_h :: declarationIR_t*) -- Decl_ok: TC_0 |- declaration_h : TC_1 declarationIR_h -- Decls_ok: TC_1 |- declaration_t* : TC_2 declarationIR_t*
11.1.2. Compile-time evaluation
Click to view the specification source
relation Decl_inst: instContext store |- declarationIR : instContext store
Instantiation statically allocates instantiated objects in the global
store. Section 11.4 describes how objects are
instantiated from a declaration.
A list of declarations is instantiated as follows:
Click to view the specification source
relation Decls_inst: instContext store |- declarationIR* : instContext store
Click to view the specification source
rulegroup Decls_inst: rule Decls_inst/nil: IC STO |- eps : IC STO rule Decls_inst/cons: IC_0 STO_0 |- declarationIR_h :: declarationIR_t* : IC_2 STO_2 -- Decl_inst: IC_0 STO_0 |- declarationIR_h : IC_1 STO_1 -- Decls_inst: IC_1 STO_1 |- declarationIR_t* : IC_2 STO_2
11.1.3. Runtime evaluation
All declarations except constant and variable declarations are evaluated at compile-time. See Section 11.2 for the runtime evaluation of variable declarations and Section 11.3 for the runtime evaluation of constant declarations.
The subsequent sections describe each kind of declaration in detail.
11.2. Variable declarations
Local variables are declared with a type, a name, and an optional initializer (as well as an optional annotation):
variableDeclaration
: annotationList type name initializerOpt ;
;
initializerOpt
: /* empty */
| initializer
;
initializer
: = expression
;
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 18.4). The language places few restrictions on the types of
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 11.4).
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.
Variable declarations can appear in the following locations within a P4 program:
-
In a block statement,
-
In a
parserstate, -
In a
controlblock’sapplysub-block, -
In the list of local declarations in a
parser, and -
In the list of local declarations in a
control.
Note that variable declarations cannot appear at the top level of a P4 program.
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.
11.2.1. Type checking
Variable declarations are type checked using the following relation:
Click to view the specification source
relation VarDecl_ok: cursor typingContext |- variableDeclaration : typingContext variableDeclarationIR
After typing, variable declarations are represented in P4IR as:
variableDeclarationIR
: annotationList typeIR nameIR initializerOptIR ;
;
initializerIR
: = typedExpressionIR
;
These are produced by the following rules:
Click to view the specification source
rulegroup VarDecl_ok: rule VarDecl_ok/non-initializer: p TC_0 |- annotationList type name `EMPTY ; : TC_1 variableDeclarationIR -- Type_ok: p TC_0 |- type : typeIR # eps -- Type_wf: $bound(p, TC_0) |- typeIR -- if $is_assignable_typeIR(typeIR) -- if nameIR = $name(name) -- if TC_1 = $add_var_t(p, TC_0, nameIR, INOUT typeIR DYN eps) -- if variableDeclarationIR = annotationList typeIR nameIR eps ; rule VarDecl_ok/initializer: p TC_0 |- annotationList type name (= expression_init) ; : TC_1 variableDeclarationIR -- Type_ok: p TC_0 |- type : typeIR # eps -- Type_wf: $bound(p, TC_0) |- typeIR -- if $is_assignable_typeIR(typeIR) -- Expr_ok: p TC_0 |- expression_init : typedExpressionIR_init -- if typedExpressionIR_init_cast = $cast_unary(typedExpressionIR_init, typeIR) -- if nameIR = $name(name) -- if TC_1 = $add_var_t(p, TC_0, nameIR, INOUT typeIR DYN eps) -- if variableDeclarationIR = annotationList typeIR nameIR (= typedExpressionIR_init_cast) ;
11.2.2. Runtime evaluation
Variable declarations are evaluated at runtime using the following relation:
Click to view the specification source
relation VarDecl_eval: cursor evalContext arch |- variableDeclarationIR : evalContext arch declarationResult
The result of evaluating a variable declaration is:
continueEmptyResult
: /* empty */
;
exitResult
: EXIT
;
abortResult
: exitResult
| rejectTransitionResult
;
declarationResult
: continueEmptyResult
| abortResult
;
See Section 13.7 for how exitResult may occur. The relation is
defined by the following rule:
Click to view the specification source
rulegroup VarDecl_eval: rule VarDecl_eval/non-initializer: p EC_0 ARCH |- annotationList typeIR nameIR eps ; : EC_1 ARCH `EMPTY -- if typeIR_subst = $subst_type_e(BLOCK, EC_0, typeIR) -- if value_init = $default(typeIR_subst) -- if EC_1 = $add_var_e(p, EC_0, ` nameIR, value_init) rule VarDecl_eval/initializer-abort: p EC_0 ARCH_0 |- annotationList typeIR nameIR (= typedExpressionIR_init) ; : EC_1 ARCH_1 abortResult -- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_init : EC_1 ARCH_1 abortResult rule VarDecl_eval/initializer-cont: p EC_0 ARCH_0 |- annotationList typeIR nameIR (= typedExpressionIR_init) ; : EC_2 ARCH_1 `EMPTY -- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_init : EC_1 ARCH_1 (` value_init) -- if EC_2 = $add_var_e(p, EC_1, ` nameIR, value_init)
11.3. Constant declarations
Constant values are defined with the syntax:
constantDeclaration
: annotationList CONST type name initializer ;
;
initializer
: = expression
;
The initializer expression must be a local compile-time known value. A
constant declaration introduces a constant whose value has the specified type.
The following are all legal constant declarations:
const bit<32> COUNTER = 32w0x0;
struct Version {
bit<32> major;
bit<32> minor;
}
const Version version = { 32w0, 32w0 };
11.3.1. Type checking
Constant declarations are type checked with the relation:
Click to view the specification source
relation ConstDecl_ok: cursor typingContext |- constantDeclaration : typingContext constantDeclarationIR
After typing, in P4IR, constant declarations have the syntax:
constantDeclarationIR
: annotationList CONST typeIR nameIR constantInitializerIR ;
;
constantInitializerIR
: = `VALUE value
;
Notice that constantInitializerIR is reduced to a value in P4IR, where
value is computed from initializer via local compile-time evaluation. It is
defined by the following rule:
Click to view the specification source
rulegroup ConstDecl_ok: rule ConstDecl_ok: p TC_0 |- annotationList CONST type name (= expression_value) ; : TC_1 constantDeclarationIR -- Type_ok: p TC_0 |- type : typeIR # eps -- Type_wf: $bound(p, TC_0) |- typeIR -- Expr_ok: p TC_0 |- expression_value : typedExpressionIR_value -- if LCTK = $ctk_of_typedExpressionIR(typedExpressionIR_value) -- if typedExpressionIR_value_cast = $cast_unary(typedExpressionIR_value, typeIR) -- Expr_eval_lctk: p TC_0 |- typedExpressionIR_value_cast ~> value -- if nameIR = $name(name) -- if TC_1 = $add_var_t(p, TC_0, nameIR, `EMPTY typeIR LCTK value) -- if constantDeclarationIR = annotationList CONST typeIR nameIR (= `VALUE value) ;
When used as a top-level declaration, constant declarations are typed with:
Click to view the specification source
rulegroup Decl_ok/constantDeclaration: rule Decl_ok/constantDeclaration: TC_0 |- constantDeclaration : TC_1 constantDeclarationIR -- ConstDecl_ok: GLOBAL TC_0 |- constantDeclaration : TC_1 constantDeclarationIR
11.3.2. Compile-time evaluation
Constant declarations are evaluated during instantiation phase with:
Click to view the specification source
relation ConstDecl_inst: cursor instContext |- constantDeclarationIR : instContext
It is defined by the following rule:
Click to view the specification source
rulegroup ConstDecl_inst: rule ConstDecl_inst: p IC_0 |- annotationList CONST typeIR nameIR (= `VALUE value) ; : IC_1 -- if IC_1 = $add_var_i(p, IC_0, nameIR, value)
When used as a top-level declaration, constant declarations are compile-time evaluated with:
Click to view the specification source
rulegroup Decl_inst/constantDeclarationIR: rule Decl_inst/constantDeclarationIR: IC_0 STO |- constantDeclarationIR : IC_1 STO -- ConstDecl_inst: GLOBAL IC_0 |- constantDeclarationIR : IC_1
11.3.3. Runtime evaluation
Constant declarations are evaluated at runtime using the following relation:
Click to view the specification source
relation ConstDecl_eval: cursor evalContext |- constantDeclarationIR : evalContext
Click to view the specification source
rulegroup ConstDecl_eval: rule ConstDecl_eval: p EC_0 |- annotationList CONST typeIR nameIR (= `VALUE value) ; : EC_1 -- if EC_1 = $add_var_e(p, EC_0, ` nameIR, value)
11.4. Instantiations
Instantiations are similar to variable declarations, but are reserved for the
types with constructors (extern objects, control blocks, parsers,
and packages):
instantiation
: annotationList type `( argumentList ) name ;
| annotationList type `( argumentList ) name objectInitializer ;
;
An instantiation is written as a constructor invocation followed by a name. Instantiations are always executed at compilation time (Section Section 7.3). 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:
// 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 */ }
}
A P4 program may not instantiate controls and parsers at the top-level. 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:
// Program
control c(/* parameters omitted */) { /* body omitted */ }
c() c1; // illegal top-level instantiation
because control c1 is instantiated at the top-level. Note that top-level
declarations of constants and instantiations of extern objects are permitted.
11.4.1. 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:
objectInitializer
: = `{ objectDeclarationList }
;
objectDeclarationList
: /* empty */
| objectDeclarationList objectDeclaration
;
objectDeclaration
: functionDeclaration
| instantiation
;
In P4IR, object initializers have the syntax:
objectInitializerIR
: = `{ objectDeclarationListIR }
;
objectDeclarationListIR = objectDeclarationIR*
objectDeclarationIR
: functionDeclarationIR
| instantiationIR
;
Abstract method implementations must use the same number of parameters, and the same parameter directions as those declared in the extern object declaration. Note that overloading of abstract methods is not allowed, thus an implementation could be matched to a declaration only with the method name.
Abstract method implementations can only use the supplied arguments or refer to
values that are in the same initializer block or the top-level scope. When
calling another method of the same instance the this keyword is used to
indicate the current object instance:
// 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];
}
}
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.
11.4.1.1. Type checking
An object declaration is type checked with the relation:
Click to view the specification source
relation ObjectDecl_ok: cursor typingContext typeFrame externMethodTypeDefEnv |- objectDeclaration : typeFrame externMethodTypeDefEnv objectDeclarationIR
It is defined by the following rules:
Click to view the specification source
rulegroup ObjectDecl_ok: rule ObjectDecl_ok/instantiation: p TC_0 typeFrame externMethodTypeDefEnv |- instantiation : typeFrame_init externMethodTypeDefEnv instantiationIR -- InstDecl_ok: p TC_0 |- instantiation : TC_1 instantiationIR -- if _ _ _ `(_) nameIR _ ; = instantiationIR -- if varTypeIR = $find_var_t(` nameIR, p, TC_1) -- if typeFrame_init = $update_map<id, varTypeIR>(typeFrame, nameIR, varTypeIR) rule ObjectDecl_ok/functionDeclaration: p TC_0 typeFrame externMethodTypeDefEnv |- annotationList (typeOrVoid name typeParameterListOpt `(parameterList)) blockStatement : typeFrame externMethodTypeDefEnv_init functionDeclarationIR -- if TC_1 = TC_0[BLOCK.KIND = EXTERN] -- if TC_2 = TC_1[BLOCK.FRAME = typeFrame] -- TypeParameterListOpt_ok: LOCAL TC_2 |- typeParameterListOpt : TC_3 typeId_expl* -- ParameterList_ok: LOCAL TC_3 |- parameterList : TC_4 parameterIR* # typeId_impl* -- Type_ok: LOCAL TC_3 |- typeOrVoid : typeIR_ret # eps -- if TC_5 = TC_4[LOCAL.KIND = (EXTERN_METHOD : typeIR_ret)] -- Block_ok: TC_5 CONT NOLOOP |- blockStatement : _ f blockStatementIR -- if f = RET \/ typeIR_ret = VOID -- if callableId = $callableId(name, parameterList) -- if nameIR = $name(name) -- if externMethodTypeDefIR = EXTERN_METHOD nameIR `<typeId_expl* , typeId_impl*> `(parameterIR*) : typeIR_ret -- CallableTypeDef_wf: $bound(p, TC_0) |- externMethodTypeDefIR -- if externMethodTypeDefEnv_init = $update_map<callableId, externMethodTypeDefIR>(externMethodTypeDefEnv, callableId, externMethodTypeDefIR) -- if functionDeclarationIR = annotationList (typeIR_ret nameIR `<typeId_expl* , typeId_impl*> `(parameterIR*)) blockStatementIR
A list of object declarations is type checked with the relation:
Click to view the specification source
relation ObjectDeclList_ok: cursor typingContext |- objectDeclarationList : typeFrame externMethodTypeDefEnv objectDeclarationListIR
11.4.1.2. Compile-time evaluation
At compile-time, an object declaration is loaded with the relation:
Click to view the specification source
relation ObjectDecl_inst: instContext store |- objectDeclarationIR : instContext store
It is defined by the following rules:
Click to view the specification source
rulegroup ObjectDecl_inst: rule ObjectDecl_inst/functionDeclarationIR: IC_0 STO |- functionDeclarationIR : IC_1 STO -- FuncDecl_inst: BLOCK IC_0 |- functionDeclarationIR : IC_1 rule ObjectDecl_inst/instantiationIR: IC_0 STO_0 |- instantiationIR : IC_1 STO_1 -- InstDecl_inst: GLOBAL IC_0 STO_0 |- instantiationIR : IC_1 STO_1 _
A list of object declarations is loaded with the relation:
Click to view the specification source
relation ObjectDecls_inst: instContext store |- objectDeclarationListIR : instContext store
11.4.2. Type checking
Instantiations are type checked with the relation:
Click to view the specification source
relation InstDecl_ok: cursor typingContext |- instantiation : typingContext instantiationIR
After type checking, in P4IR, instantiations have the syntax:
instantiationIR
: annotationList typeIR constructorTargetIR `( argumentListIR ) nameIR
objectInitializerOptIR ;
;
Click to view the specification source
rulegroup InstDecl_ok:
rule InstDecl_ok/non-objectInitializer-prefixedTypeName:
p TC_0 |- annotationList prefixedTypeName `(argumentList) name ; : TC_1 instantiationIR
-- InstDecl_non_objectInitializer_ok: p TC_0 |- annotationList prefixedTypeName `<`EMPTY> `(argumentList) name ; : TC_1 instantiationIR
rule InstDecl_ok/non-objectInitializer-specializedType:
p TC_0 |- annotationList (prefixedTypeName `<typeArgumentList>) `(argumentList) name ; : TC_1 instantiationIR
-- InstDecl_non_objectInitializer_ok: p TC_0 |- annotationList prefixedTypeName `<typeArgumentList> `(argumentList) name ; : TC_1 instantiationIR
rule InstDecl_ok/objectInitializer-prefixedTypeName:
p TC_0 |- annotationList prefixedTypeName `(argumentList) name (= `{objectDeclarationList}) ; : TC_1 instantiationIR
-- InstDecl_objectInitializer_ok: p TC_0 |- annotationList prefixedTypeName `<`EMPTY> `(argumentList) name = `{objectDeclarationList} ; : TC_1 instantiationIR
rule InstDecl_ok/objectInitializer-specializedType:
p TC_0 |- annotationList (prefixedTypeName `<typeArgumentList>) `(argumentList) name (= `{objectDeclarationList}) ; : TC_1 instantiationIR
-- InstDecl_objectInitializer_ok: p TC_0 |- annotationList prefixedTypeName `<typeArgumentList> `(argumentList) name = `{objectDeclarationList} ; : TC_1 instantiationIR
When used as a top-level declaration, instantiations are typed with:
Click to view the specification source
rulegroup Decl_ok/instantiation: rule Decl_ok/instantiation: TC_0 |- instantiation : TC_1 instantiationIR -- InstDecl_ok: GLOBAL TC_0 |- instantiation : TC_1 instantiationIR
There are two sub-relations, depending on whether object initializers were provided or not.
When object initializers are absent
Click to view the specification source
relation InstDecl_non_objectInitializer_ok: cursor typingContext |- annotationList prefixedTypeName `<typeArgumentList> `(argumentList) name ; : typingContext instantiationIR
Click to view the specification source
rulegroup InstDecl_non_objectInitializer_ok: rule InstDecl_non_objectInitializer_ok: p TC_0 |- annotationList prefixedTypeName `<typeArgumentList> `(argumentList) name ; : TC_1 instantiationIR -- TypeArgumentList_ok: p TC_0 |- typeArgumentList : typeArgumentIR* # typeId_impl* -- ArgumentList_ok: p TC_0 |- argumentList : argumentIR* -- if prefixedNameIR = $prefixedTypeName(prefixedTypeName) -- if constructorTargetIR = prefixedNameIR `<typeArgumentIR*> -- ConstructorType_ok: p TC_0 |- constructorTargetIR `(argumentIR*) : constructorTypeIR `<# typeId_inserted*> `(# id_default* # id_optional*) -- if typeId_infer* = typeId_impl* ++ typeId_inserted* -- Inst_ok: p TC_0 NAMED |- constructorTypeIR `<typeArgumentIR* # typeId_infer*> `(argumentIR* # id_default* # id_optional*) : typeIR_object `<typeArgumentIR_inferred*> `(argumentIR_cast*) -- if nameIR = $name(name) -- if TC_1 = $add_var_t(p, TC_0, nameIR, `EMPTY typeIR_object CTK eps) -- if constructorTargetIR_inferred = prefixedNameIR `<typeArgumentIR_inferred*> -- if instantiationIR = annotationList typeIR_object constructorTargetIR_inferred `(argumentIR_cast*) nameIR eps ;
When object initializers are present
Click to view the specification source
relation InstDecl_objectInitializer_ok: cursor typingContext |- annotationList prefixedTypeName `<typeArgumentList> `(argumentList) name = `{objectDeclarationList} ; : typingContext instantiationIR
Click to view the specification source
rulegroup InstDecl_objectInitializer_ok:
rule InstDecl_objectInitializer_ok:
p TC_0 |- annotationList prefixedTypeName `<typeArgumentList> `(argumentList) name = `{objectDeclarationList} ; : TC_2 instantiationIR
-- TypeArgumentList_ok: p TC_0 |- typeArgumentList : typeArgumentIR* # typeId_impl*
-- ArgumentList_ok: p TC_0 |- argumentList : argumentIR*
-- if prefixedNameIR = $prefixedTypeName(prefixedTypeName)
-- if constructorTargetIR = prefixedNameIR `<typeArgumentIR*>
-- ConstructorType_ok: p TC_0 |- constructorTargetIR `(argumentIR*) : constructorTypeIR `<# typeId_inserted*> `(# id_default* # id_optional*)
-- if typeId_infer* = typeId_impl* ++ typeId_inserted*
-- Inst_ok: p TC_0 NAMED |- constructorTypeIR `<typeArgumentIR* # typeId_infer*> `(argumentIR* # id_default* # id_optional*) : typeIR_object `<typeArgumentIR_inferred*> `(argumentIR_cast*)
-- if EXTERN typeId_extern `<typeIR_arg*> externMethodTypeDefEnv_extern = $unroll_typeIR(typeIR_object)
-- if TC_1 = $add_var_t(LOCAL, TC_0, "this", `EMPTY typeIR_object CTK eps)
-- ObjectDeclList_ok: p TC_1 |- objectDeclarationList : typeFrame_init externMethodTypeDefEnv_init objectDeclarationIR*
-- if externMethodTypeDefEnv_init_subst = $subst_externMethodTypeDefEnv(externMethodTypeDefEnv_extern, externMethodTypeDefEnv_init)
-- if typeIR_object_init = EXTERN typeId_extern `<typeIR_arg*> externMethodTypeDefEnv_init_subst
-- if $is_concrete_extern_object(typeIR_object_init)
-- if nameIR = $name(name)
-- if TC_2 = $add_var_t(p, TC_0, nameIR, `EMPTY typeIR_object_init CTK eps)
-- if constructorTargetIR_inferred = prefixedNameIR `<typeArgumentIR_inferred*>
-- if instantiationIR = annotationList typeIR_object_init constructorTargetIR_inferred `(argumentIR_cast*) nameIR (= `{objectDeclarationIR*}) ;
11.4.3. Compile-time evaluation
Instantiations are evaluated at compile-time with the relation:
Click to view the specification source
relation InstDecl_inst: cursor instContext store |- instantiationIR : instContext store constantDeclarationIR
Click to view the specification source
rulegroup InstDecl_inst:
rule InstDecl_inst/extern:
p IC_0 STO_0 |- annotationList typeIR constructorTargetIR `(argumentListIR) nameIR objectInitializerOptIR ; : IC_1 STO_3 constantDeclarationIR
-- if prefixedNameIR `<typeArgumentListIR> = constructorTargetIR
-- Constructor_inst: p IC_0 |- prefixedNameIR `<typeArgumentListIR> `(argumentListIR) : constructorDef `<typeArgumentListIR_inst> `(# id_default* # id_optional*)
-- if IC_inner = $enter_path_i(IC_0, nameIR)
-- Constructor_call: p IC_inner STO_0 |- constructorDef `<typeArgumentListIR_inst> `(argumentListIR # id_default* # id_optional*) : STO_1 object
-- if EXTERN typeId `<theta> `{objectState frame externMethodDefEnv} = object
-- if IC_decl = $inherit_i(GLOBAL, IC_0)
-- if objectDeclarationListIR = $flatten_objectInitializerOptIR(objectInitializerOptIR)
-- ObjectDecls_inst: IC_decl STO_1 |- objectDeclarationListIR : IC_decl_post STO_2
-- if frame_merged = $merge_frames(frame, IC_decl_post.BLOCK.FRAME)
-- if externMethodDefEnv_merged = $merge_externMethodDefEnvs(externMethodDefEnv, IC_decl_post.BLOCK.CALLABLE)
-- if object_merged = EXTERN typeId `<theta> `{objectState frame_merged externMethodDefEnv_merged}
-- if objectId = IC_0.PATH ++ nameIR
-- if STO_3 = $add_store(STO_2, objectId, object_merged)
-- if IC_1 = $add_var_i(p, IC_0, nameIR, REF objectId)
-- if constantDeclarationIR = `EMPTY CONST typeIR nameIR (= `VALUE (REF objectId)) ;
rule InstDecl_inst/non-extern:
p IC_0 STO_0 |- annotationList typeIR constructorTargetIR `(argumentListIR) nameIR objectInitializerOptIR ; : IC_1 STO_2 constantDeclarationIR
-- if prefixedNameIR `<typeArgumentListIR> = constructorTargetIR
-- Constructor_inst: p IC_0 |- prefixedNameIR `<typeArgumentListIR> `(argumentListIR) : constructorDef `<typeArgumentListIR_inst> `(# id_default* # id_optional*)
-- if IC_inner = $enter_path_i(IC_0, nameIR)
-- Constructor_call: p IC_inner STO_0 |- constructorDef `<typeArgumentListIR_inst> `(argumentListIR # id_default* # id_optional*) : STO_1 object
-- if ~(object <: externObject)
-- if objectId = IC_0.PATH ++ nameIR
-- if STO_2 = $add_store(STO_1, objectId, object)
-- if IC_1 = $add_var_i(p, IC_0, nameIR, REF objectId)
-- if constantDeclarationIR = `EMPTY CONST typeIR nameIR (= `VALUE (REF objectId)) ;
When used as a top-level declaration, instantiations are evaluated with:
Click to view the specification source
rulegroup Decl_inst/instantiationIR: rule Decl_inst/instantiationIR: IC_0 STO_0 |- instantiationIR : IC_1 STO_1 -- InstDecl_inst: GLOBAL IC_0 STO_0 |- instantiationIR : IC_1 STO_1 _
11.5. Function declarations
Function declarations introduce user-defined functions into a P4 program. Section 9.4.1 describes the semantics of user-defined functions in P4.
functionPrototype
: typeOrVoid name typeParameterListOpt `( parameterList )
;
functionDeclaration
: annotationList functionPrototype blockStatement
;
11.5.1. Type checking
Function declarations are type checked with the relation:
Click to view the specification source
relation FuncDecl_ok: cursor typingContext |- functionDeclaration : typingContext functionDeclarationIR
After type checking, function declarations are represented in P4IR as:
functionPrototypeIR
: typeIR nameIR `< typeParameterListIR , typeParameterListIR >
`( parameterListIR )
;
functionDeclarationIR
: annotationList functionPrototypeIR blockStatementIR
;
They are produced by the following rule:
Click to view the specification source
rulegroup FuncDecl_ok: rule FuncDecl_ok: p TC_0 |- annotationList (typeOrVoid name typeParameterListOpt `(parameterList)) blockStatement : TC_4 functionDeclarationIR -- TypeParameterListOpt_ok: LOCAL TC_0 |- typeParameterListOpt : TC_1 typeId_expl* -- Type_ok: LOCAL TC_1 |- typeOrVoid : typeIR_ret # eps -- ParameterList_ok: LOCAL TC_1 |- parameterList : TC_2 parameterIR* # typeId_impl* -- if TC_3 = TC_2[LOCAL.KIND = FUNCTION : typeIR_ret] -- Block_ok: TC_3 CONT NOLOOP |- blockStatement : _ f blockStatementIR -- if f = RET \/ typeIR_ret = VOID -- if callableId = $callableId(name, parameterList) -- if nameIR = $name(name) -- if definedFunctionTypeDefIR = FUNCTION nameIR `<typeId_expl* , typeId_impl*> `(parameterIR*) : typeIR_ret -- CallableTypeDef_wf: $bound(p, TC_0) |- definedFunctionTypeDefIR -- if TC_4 = $add_callableDef_overload_t(p, TC_0, callableId, definedFunctionTypeDefIR) -- if functionDeclarationIR = annotationList (typeIR_ret nameIR `<typeId_expl* , typeId_impl*> `(parameterIR*)) blockStatementIR
When used as a top-level declaration, function declarations are typed with:
Click to view the specification source
rulegroup Decl_ok/functionDeclaration: rule Decl_ok/functionDeclaration: TC_0 |- functionDeclaration : TC_1 functionDeclarationIR -- FuncDecl_ok: GLOBAL TC_0 |- functionDeclaration : TC_1 functionDeclarationIR
11.5.2. Compile-time evaluation
At compile-time, function declarations are loaded into the context with the relation:
Click to view the specification source
relation FuncDecl_inst: cursor instContext |- functionDeclarationIR : instContext
It is defined by the following rule:
Click to view the specification source
rulegroup FuncDecl_inst: rule FuncDecl_inst: p IC_0 |- annotationList (typeIR nameIR (`<typeParameterListIR_expl , typeParameterListIR_impl>) `(parameterListIR)) blockStatementIR : IC_1 -- if callableId = $callableId_IR(nameIR, parameterListIR) -- if typeParameterListIR = typeParameterListIR_expl ++ typeParameterListIR_impl -- if definedFunctionDef = FUNCTION nameIR `<typeParameterListIR> `(parameterListIR) blockStatementIR -- if IC_1 = $add_callableDef_overload_i(p, IC_0, callableId, definedFunctionDef)
When used as a top-level declaration, function declarations are loaded with:
Click to view the specification source
rulegroup Decl_inst/functionDeclarationIR: rule Decl_inst/functionDeclarationIR: IC_0 STO |- functionDeclarationIR : IC_1 STO -- FuncDecl_inst: GLOBAL IC_0 |- functionDeclarationIR : IC_1
11.6. Action declarations
action declarations introduce actions:
actionDeclaration
: annotationList ACTION name `( parameterList ) blockStatement
;
Section 9.3 describes the semantics of actions.
11.6.1. Type checking
Action declarations are type checked with the relation:
Click to view the specification source
relation ActionDecl_ok: cursor typingContext |- actionDeclaration : typingContext actionDeclarationIR
After type checking, action declarations are represented in P4IR as:
actionDeclarationIR
: annotationList ACTION nameIR `( parameterListIR ) blockStatementIR
;
These are produced by the following rule:
Click to view the specification source
rulegroup ActionDecl_ok: rule ActionDecl_ok: p TC_0 |- annotationList ACTION name `(parameterList) blockStatement : TC_3 actionDeclarationIR -- if TC_1 = TC_0[LOCAL.KIND = ACTION] -- ParameterList_ok: LOCAL TC_1 |- parameterList : TC_2 parameterIR* # eps -- Block_ok: TC_2 CONT NOLOOP |- blockStatement : _ _ blockStatementIR -- if callableId = $callableId(name, parameterList) -- if nameIR = $name(name) -- if actionTypeDefIR = annotationList ACTION nameIR `(parameterIR*) -- CallableTypeDef_wf: $bound(p, TC_0) |- actionTypeDefIR -- if TC_3 = $add_callableDef_non_overload_t(p, TC_0, callableId, actionTypeDefIR) -- if actionDeclarationIR = annotationList ACTION nameIR `(parameterIR*) blockStatementIR
When used as a top-level declaration, action declarations are typed with:
Click to view the specification source
rulegroup Decl_ok/actionDeclaration: rule Decl_ok/actionDeclaration: TC_0 |- actionDeclaration : TC_1 actionDeclarationIR -- ActionDecl_ok: GLOBAL TC_0 |- actionDeclaration : TC_1 actionDeclarationIR
11.6.2. Compile-time evaluation
At compile-time, action declarations are loaded into the context with the relation:
Click to view the specification source
relation ActionDecl_inst: cursor instContext |- actionDeclarationIR : instContext
It is defined by the following rule:
Click to view the specification source
rulegroup ActionDecl_inst: rule ActionDecl_inst: p IC_0 |- annotationList ACTION nameIR `(parameterListIR) blockStatementIR : IC_1 -- if callableId = $callableId_IR(nameIR, parameterListIR) -- if actionDef = ACTION nameIR `(parameterListIR) blockStatementIR -- if IC_1 = $add_callableDef_non_overload_i(p, IC_0, callableId, actionDef)
When used as a top-level declaration, action declarations are loaded with:
Click to view the specification source
rulegroup Decl_inst/actionDeclarationIR: rule Decl_inst/actionDeclarationIR: IC_0 STO |- actionDeclarationIR : IC_1 STO -- ActionDecl_inst: GLOBAL IC_0 |- actionDeclarationIR : IC_1
11.7. Error declarations
errorDeclaration
: ERROR `{ nameList }
;
error declarations introduce error elements. See Section 8.2.4 for more
details on error elements.
11.7.1. Type checking
After type checking, an error declaration is represented as:
errorDeclarationIR
: ERROR `{ nameListIR }
;
Click to view the specification source
rulegroup Decl_ok/errorDeclaration:
rule Decl_ok/errorDeclaration:
TC_0 |- ERROR `{nameList} : TC_1 (ERROR `{nameIR*})
-- if name* = $flatten_nameList(nameList)
-- (if (nameIR = $name(name)))*
-- if $distinct_<nameIR>(nameIR*)
-- (if (nameIR_error = "error." ++ nameIR))*
-- (if (value_error = ERROR . nameIR))*
-- (if (varTypeIR = `EMPTY ERROR LCTK value_error))*
-- if TC_1 = $add_vars_t(GLOBAL, TC_0, nameIR_error*, varTypeIR*)
11.7.2. Compile-time evaluation
At comile-time, error declarations are loaded into the context by:
Click to view the specification source
rulegroup Decl_inst/errorDeclarationIR:
rule Decl_inst/errorDeclarationIR:
IC_0 STO |- ERROR `{nameIR*} : IC_1 STO
-- (if (nameIR_error = $concat_text(["error.", nameIR])))*
-- (if (value_error = ERROR . nameIR))*
-- if IC_1 = $add_vars_i(GLOBAL, IC_0, nameIR_error*, value_error*)
11.8. Match kind declarations
matchKindDeclaration
: MATCH_KIND `{ nameList trailingCommaOpt }
;
Similar to error declarations, match_kind declarations introduce
match kinds. See Section 8.2.5 for more details on match kinds.
11.8.1. Type checking
After type checking, an match_kind declaration is represented as:
matchKindDeclarationIR
: MATCH_KIND `{ nameListIR }
;
Click to view the specification source
rulegroup Decl_ok/matchKindDeclaration:
rule Decl_ok/matchKindDeclaration:
TC_0 |- MATCH_KIND `{nameList _} : TC_1 (MATCH_KIND `{nameIR*})
-- if name* = $flatten_nameList(nameList)
-- (if (nameIR = $name(name)))*
-- if $distinct_<nameIR>(nameIR*)
-- (if (value_match_kind = MATCH_KIND . nameIR))*
-- (if (varTypeIR = `EMPTY MATCH_KIND LCTK value_match_kind))*
-- if TC_1 = $add_vars_t(GLOBAL, TC_0, nameIR*, varTypeIR*)
11.8.2. Compile-time evaluation
At comile-time, match kind declarations are loaded into the context by:
Click to view the specification source
rulegroup Decl_inst/matchKindDeclarationIR:
rule Decl_inst/matchKindDeclarationIR:
IC_0 STO |- MATCH_KIND `{nameIR*} : IC_1 STO
-- (if (value_match_kind = MATCH_KIND . nameIR))*
-- if IC_1 = $add_vars_i(GLOBAL, IC_0, nameIR*, value_match_kind*)
11.9. Extern function declarations
externFunctionDeclaration
: annotationList EXTERN functionPrototype ;
;
An extern function declaration introduces an extern function. See Section 9.4.2 for how extern functions are internally represented.
11.9.1. Type checking
After type checking, an extern function declaration is represented in P4IR as:
externFunctionDeclarationIR
: annotationList EXTERN functionPrototypeIR ;
;
Click to view the specification source
rulegroup Decl_ok/externFunctionDeclaration: rule Decl_ok/externFunctionDeclaration: TC_0 |- annotationList EXTERN (typeOrVoid name typeParameterListOpt `(parameterList)) ; : TC_4 externFunctionDeclarationIR -- TypeParameterListOpt_ok: LOCAL TC_0 |- typeParameterListOpt : TC_1 typeId_expl* -- ParameterList_ok: LOCAL TC_1 |- parameterList : TC_2 parameterIR* # typeId_impl* -- Type_ok: LOCAL TC_1 |- typeOrVoid : typeIR_ret # eps -- if TC_3 = TC_2[LOCAL.KIND = EXTERN_FUNCTION : typeIR_ret] -- if callableId = $callableId(name, parameterList) -- if nameIR = $name(name) -- if externFunctionTypeDefIR = EXTERN_FUNCTION nameIR `<typeId_expl* , typeId_impl*> `(parameterIR*) : typeIR_ret -- CallableTypeDef_wf: $bound(GLOBAL, TC_0) |- externFunctionTypeDefIR -- if TC_4 = $add_callableDef_overload_t(GLOBAL, TC_0, callableId, externFunctionTypeDefIR) -- if externFunctionDeclarationIR = annotationList EXTERN (typeIR_ret nameIR `<typeId_expl* , typeId_impl*> `(parameterIR*)) ;
11.9.2. Compile-time evaluation
At comile-time, extern function declarations are loaded into the context by:
Click to view the specification source
rulegroup Decl_inst/externFunctionDeclarationIR: rule Decl_inst/externFunctionDeclarationIR: IC_0 STO |- annotationList EXTERN (typeIR nameIR `<typeParameterListIR_expl , typeParameterListIR_impl> `(parameterListIR)) ; : IC_1 STO -- if callableId = $callableId_IR(nameIR, parameterListIR) -- if typeParameterListIR = typeParameterListIR_expl ++ typeParameterListIR_impl -- if externFunctionDef = EXTERN_FUNCTION nameIR `<typeParameterListIR> `(parameterListIR) -- if IC_1 = $add_callableDef_overload_i(GLOBAL, IC_0, callableId, externFunctionDef)
11.10. Extern object declarations
An extern object declaration introduces an extern object.
externObjectDeclaration
: annotationList EXTERN nonTypeName typeParameterListOpt
`{ externConstructorOrMethodPrototypeList }
;
externConstructorOrMethodPrototype
: externConstructorPrototype
| externMethodPrototype
;
externConstructorOrMethodPrototypeList
: /* empty */
| externConstructorOrMethodPrototypeList externConstructorOrMethodPrototype
;
See Section 10.3 for how extern objects are internally represented. An extern object declaration includes its constructors and methods, to be explained in the following sections.
11.10.1. Extern methods
Extern objects can have methods, which are declared using the following syntax:
externMethodPrototype
: annotationList functionPrototype ;
| annotationList ABSTRACT functionPrototype ;
;
See Section 9.5.1 for details on how extern methods are represented internally.
11.10.1.1. Type checking
Extern methods are type checked using the following relation:
Click to view the specification source
relation ExternMethod_ok: typingContext typeId |- externMethodPrototype : externMethodPrototypeIR
After type checking, in P4IR, an extern method declaration is represented as:
externMethodPrototypeIR
: annotationList functionPrototypeIR ;
| annotationList ABSTRACT functionPrototypeIR ;
;
These are produced by:
Click to view the specification source
rulegroup ExternMethod_ok: rule ExternMethod_ok/non-abstract: TC_0 typeId_extern |- annotationList functionPrototype ; : externMethodPrototypeIR -- if typeOrVoid name typeParameterListOpt `(parameterList) = functionPrototype -- if nameIR = $name(name) -- if nameIR =/= typeId_extern -- TypeParameterListOpt_ok: LOCAL TC_0 |- typeParameterListOpt : TC_1 typeId_expl* -- Type_ok: LOCAL TC_1 |- typeOrVoid : typeIR_ret # eps -- if TC_2 = TC_1[LOCAL.KIND = EXTERN_METHOD : typeIR_ret] -- ParameterList_ok: LOCAL TC_2 |- parameterList : TC_3 parameterIR* # typeId_impl* -- if externMethodTypeDefIR = EXTERN_METHOD nameIR `<typeId_expl* , typeId_impl*> `(parameterIR*) : typeIR_ret -- CallableTypeDef_wf: $bound(BLOCK, TC_0) |- externMethodTypeDefIR -- if externMethodPrototypeIR = annotationList (typeIR_ret nameIR `<typeId_expl* , typeId_impl*> `(parameterIR*)) ; rule ExternMethod_ok/abstract: TC_0 typeId_extern |- annotationList ABSTRACT functionPrototype ; : externMethodPrototypeIR -- if typeOrVoid name typeParameterListOpt `(parameterList) = functionPrototype -- if nameIR = $name(name) -- if nameIR =/= typeId_extern -- TypeParameterListOpt_ok: LOCAL TC_0 |- typeParameterListOpt : TC_1 typeId_expl* -- Type_ok: LOCAL TC_1 |- typeOrVoid : typeIR_ret # eps -- if TC_2 = TC_1[LOCAL.KIND = EXTERN_METHOD ABSTRACT : typeIR_ret] -- ParameterList_ok: LOCAL TC_2 |- parameterList : TC_3 parameterIR* # typeId_impl* -- if externMethodTypeDefIR = EXTERN_METHOD ABSTRACT nameIR `<typeId_expl* , typeId_impl*> `(parameterIR*) : typeIR_ret -- CallableTypeDef_wf: $bound(BLOCK, TC_0) |- externMethodTypeDefIR -- if externMethodPrototypeIR = annotationList ABSTRACT (typeIR_ret nameIR `<typeId_expl* , typeId_impl*> `(parameterIR*)) ;
11.10.2. Extern constructors
Extern objects may have constructors, which are declared using the following syntax:
externConstructorPrototype
: annotationList typeIdentifier `( parameterList ) ;
;
11.10.2.1. Type checking
Extern constructors are type checked using the following relation:
Click to view the specification source
relation ExternConstructor_ok: typingContext typeId |- externConstructorPrototype : externConstructorPrototypeIR
After type checking, in P4IR, an extern constructor declaration is represented as:
externConstructorPrototypeIR
: annotationList nameIR `< , typeParameterListIR > `( parameterListIR ) ;
;
These are produced by:
Click to view the specification source
rulegroup ExternConstructor_ok: rule ExternConstructor_ok: TC_0 typeId_extern |- annotationList typeIdentifier `(parameterList) ; : externConstructorPrototypeIR -- if nameIR = $name(typeIdentifier) -- if nameIR = typeId_extern -- ConstructorParameterListOpt_ok: TC_0 |- `(parameterList) : TC_1 constructorParameterIR* # typeId_impl* -- if externObjectTypeDefIR = $find_typeDef_t(GLOBAL, TC_0, ` typeId_extern) -- if (typeId_expl*, eps) = $typeParameterListIR_of_typeDefIR(externObjectTypeDefIR) -- if typeIR_extern = $typeIR_of_typeDefIR(externObjectTypeDefIR) -- if constructorTypeDefIR = CONSTRUCTOR `<typeId_expl* , typeId_impl*> `(constructorParameterIR*) : typeIR_extern -- ConstructorTypeDef_wf: $bound(BLOCK, TC_0) |- constructorTypeDefIR -- if externConstructorPrototypeIR = annotationList nameIR `<, typeId_impl*> `(constructorParameterIR*) ;
11.10.3. Type checking
After type checking, an extern object declaration is represented in P4IR as:
externObjectDeclarationIR
: annotationList EXTERN nameIR `< typeParameterListIR , >
`{ externConstructorPrototypeListIR externMethodPrototypeListIR }
;
externConstructorPrototypeIR
: annotationList nameIR `< , typeParameterListIR > `( parameterListIR ) ;
;
externMethodPrototypeIR
: annotationList functionPrototypeIR ;
| annotationList ABSTRACT functionPrototypeIR ;
;
Click to view the specification source
rulegroup Decl_ok/externObjectDeclaration:
rule Decl_ok/externObjectDeclaration:
TC_0 |- annotationList EXTERN nonTypeName typeParameterListOpt `{externConstructorOrMethodPrototypeList} : TC_6 externObjectDeclarationIR
-- if (externConstructorPrototype*, externMethodPrototype*) = $split_externConstructorOrMethodPrototypeList(externConstructorOrMethodPrototypeList)
-- if TC_1 = TC_0[BLOCK.KIND = EXTERN]
-- TypeParameterListOpt_ok: BLOCK TC_1 |- typeParameterListOpt : TC_2 typeId_expl*
-- if nameIR = $name(nonTypeName)
-- (ExternMethod_ok: TC_2 nameIR |- externMethodPrototype : externMethodPrototypeIR)*
-- (if (callableId_method = $callableId_of_externMethodPrototype(externMethodPrototype)))*
-- if $distinct_<callableId>(callableId_method*)
-- (if (externMethodTypeDefIR = $type_of_externMethodPrototypeIR(externMethodPrototypeIR)))*
-- if externMethodTypeDefEnv = `{(callableId_method : externMethodTypeDefIR)*}
-- if typeDefIR_extern = EXTERN nameIR `<typeId_expl* , eps> externMethodTypeDefEnv
-- if TC_3 = $add_typeDef_t(GLOBAL, TC_0, nameIR, typeDefIR_extern)
-- if TC_4 = TC_3[BLOCK.KIND = EXTERN]
-- if TC_5 = $add_typeParameters_t(BLOCK, TC_4, typeId_expl*)
-- (ExternConstructor_ok: TC_5 nameIR |- externConstructorPrototype : externConstructorPrototypeIR)*
-- (if (constructorId = $constructorId_of_externConstructorPrototype(externConstructorPrototype)))*
-- if typeIR_extern = $typeIR_of_typeDefIR(typeDefIR_extern)
-- (if (constructorTypeDefIR = $constructorTypeDef_of_externConstructorPrototypeIR(typeId_expl*, typeIR_extern, externConstructorPrototypeIR)))*
-- if TC_6 = $add_constructorDefs_t(TC_3, constructorId*, constructorTypeDefIR*)
-- if externObjectDeclarationIR = annotationList EXTERN nameIR `<typeId_expl* ,> `{externConstructorPrototypeIR* externMethodPrototypeIR*}
11.10.4. Compile-time evaluation
At comile-time, extern object declarations are loaded into the context by:
Click to view the specification source
rulegroup Decl_inst/externObjectDeclarationIR:
rule Decl_inst/non-constructor:
IC_0 STO |- annotationList EXTERN nameIR `<typeParameterListIR ,> `{eps externMethodPrototypeIR*} : IC_2 STO
-- (if (callableId_method = $callableId_of_externMethodPrototypeIR(externMethodPrototypeIR)))*
-- (if (externMethodTypeDefIR = $type_of_externMethodPrototypeIR(externMethodPrototypeIR)))*
-- if externMethodTypeDefEnv = `{(callableId_method : externMethodTypeDefIR)*}
-- if externObjectTypeDefIR = EXTERN nameIR `<typeParameterListIR , eps> externMethodTypeDefEnv
-- if IC_1 = $add_typeDef_i(GLOBAL, IC_0, nameIR, externObjectTypeDefIR)
-- if callableId_constructor = $callableId_IR(nameIR, eps)
-- if constructorDef = EXTERN nameIR `<typeParameterListIR> `(eps) `{externMethodPrototypeIR*}
-- if IC_2 = $add_constructorDef_i(IC_1, callableId_constructor, constructorDef)
rule Decl_inst/constructor:
IC_0 STO |- annotationList EXTERN nameIR `<typeParameterListIR ,> `{externConstructorPrototypeIR* externMethodPrototypeIR*} : IC_2 STO
-- if externConstructorPrototypeIR* =/= eps
-- (if (callableId_method = $callableId_of_externMethodPrototypeIR(externMethodPrototypeIR)))*
-- (if (externMethodTypeDefIR = $type_of_externMethodPrototypeIR(externMethodPrototypeIR)))*
-- if externMethodTypeDefEnv = `{(callableId_method : externMethodTypeDefIR)*}
-- if externObjectTypeDefIR = EXTERN nameIR `<typeParameterListIR , eps> externMethodTypeDefEnv
-- if IC_1 = $add_typeDef_i(GLOBAL, IC_0, nameIR, externObjectTypeDefIR)
-- (if (callableId_constructor = $callableId_of_externConstructorPrototypeIR(externConstructorPrototypeIR)))*
-- (if (constructorDef = $constructor_of_externConstructorPrototypeIR(typeParameterListIR, externMethodPrototypeIR*, externConstructorPrototypeIR)))*
-- if IC_2 = $add_constructorDefs_i(IC_1, callableId_constructor*, constructorDef*)
11.11. Parser declarations
A parser declaration introduces a programmable parser:
parserDeclaration
: annotationList PARSER name typeParameterListOpt `( parameterList )
constructorParameterListOpt
`{ parserLocalDeclarationList parserStateList }
;
parserLocalDeclaration
: constantDeclaration
| instantiation
| variableDeclaration
| valueSetDeclaration
;
parserState
: annotationList STATE name `{ parserStatementList transitionStatement }
;
11.11.1. Type checking
After type checking, a parser declaration is represented in P4IR as:
parserDeclarationIR
: annotationList PARSER nameIR `< typeParameterListIR , typeParameterListIR >
`( parameterListIR ) `( constructorParameterListIR )
`{ parserLocalDeclarationListIR parserStateListIR }
;
parserLocalDeclarationIR
: constantDeclarationIR
| instantiationIR
| variableDeclarationIR
| valueSetDeclarationIR
;
parserStateIR
: annotationList STATE nameIR
`{ parserStatementListIR transitionStatementIR }
;
Click to view the specification source
rulegroup Decl_ok/parserDeclaration:
rule Decl_ok/parserDeclaration:
TC_0 |- annotationList PARSER name typeParameterListOpt `(parameterList) constructorParameterListOpt `{parserLocalDeclarationList parserStateList} : TC_7 parserDeclarationIR
-- if TC_1 = TC_0[BLOCK.KIND = PARSER]
-- TypeParameterListOpt_ok: BLOCK TC_1 |- typeParameterListOpt : TC_2 typeId_expl*
-- ConstructorParameterListOpt_ok: TC_2 |- constructorParameterListOpt : TC_3 constructorParameterIR* # typeId_impl*
-- ParameterList_ok: BLOCK TC_3 |- parameterList : TC_4 parameterIR* # eps
-- ParserLocalDeclList_ok: TC_4 |- parserLocalDeclarationList : TC_5 parserLocalDeclarationIR*
-- if TC_6 = TC_5[LOCAL.KIND = PARSER_STATE]
-- ParserStateList_ok: TC_6 |- parserStateList : parserStateIR*
-- if callableTypeDefIR = PARSER_APPLY `(parameterIR*)
-- CallableTypeDef_wf: $bound(BLOCK, TC_2) |- callableTypeDefIR
-- if nameIR = $name(name)
-- if constructorId = $constructorId(name, constructorParameterListOpt)
-- if typeDefIR_parser = PARSER nameIR `<typeId_expl* , typeId_impl*> `(parameterIR*)
-- if typeIR_parser = $typeIR_of_typeDefIR(typeDefIR_parser)
-- if constructorTypeDefIR = CONSTRUCTOR `<typeId_expl* , typeId_impl*> `(constructorParameterIR*) : typeIR_parser
-- ConstructorTypeDef_wf: $bound(GLOBAL, TC_0) |- constructorTypeDefIR
-- if TC_7 = $add_constructorDef_t(TC_0, constructorId, constructorTypeDefIR)
-- if parserDeclarationIR = annotationList PARSER nameIR `<typeId_expl* , typeId_impl*> `(parameterIR*) `(constructorParameterIR*) `{parserLocalDeclarationIR* parserStateIR*}
11.11.2. Compile-time evaluation
At compile-time, parser declarations are loaded into the context as parser object constructors.
Click to view the specification source
rulegroup Decl_inst/parserDeclarationIR:
rule Decl_inst/parserDeclarationIR:
IC_0 STO |- annotationList PARSER nameIR `<typeParameterListIR_expl , typeParameterListIR_impl> `(parameterListIR) `(constructorParameterListIR) `{parserLocalDeclarationListIR parserStateListIR} : IC_1 STO
-- if callableId = $callableId_IR(nameIR, constructorParameterListIR)
-- if typeParameterListIR = typeParameterListIR_expl ++ typeParameterListIR_impl
-- if constructorDef = PARSER `<typeParameterListIR> `(parameterListIR) `(constructorParameterListIR) `{parserLocalDeclarationListIR parserStateListIR}
-- if IC_1 = $add_constructorDef_i(IC_0, callableId, constructorDef)
When instantiated, an instance of a parser declaration is created:
parserObject
: PARSER `< theta > `( parameterListIR )
`{ frame parserLocalDeclarationListIR stateEnv }
;
See Section 10.4 for details about parser objects.
11.12. Control declarations
A control declaration introduces a control block:
controlDeclaration
: annotationList CONTROL name typeParameterListOpt `( parameterList )
constructorParameterListOpt
`{ controlLocalDeclarationList APPLY controlBody }
;
controlLocalDeclaration
: constantDeclaration
| instantiation
| variableDeclaration
| actionDeclaration
| tableDeclaration
;
controlBody = blockStatement
11.12.1. Type checking
After type checking, a control declaration is represented in P4IR as:
controlDeclarationIR
: annotationList CONTROL nameIR
`< typeParameterListIR , typeParameterListIR > `( parameterListIR )
`( constructorParameterListIR )
`{ controlLocalDeclarationListIR APPLY controlBodyIR }
;
controlLocalDeclarationIR
: constantDeclarationIR
| instantiationIR
| variableDeclarationIR
| actionDeclarationIR
| tableDeclarationIR
;
controlBodyIR = blockStatementIR
Click to view the specification source
rulegroup Decl_ok/controlDeclaration:
rule Decl_ok/controlDeclaration:
TC_0 |- annotationList CONTROL name typeParameterListOpt `(parameterList) constructorParameterListOpt `{controlLocalDeclarationList APPLY controlBody} : TC_7 controlDeclarationIR
-- if TC_1 = TC_0[BLOCK.KIND = CONTROL]
-- TypeParameterListOpt_ok: BLOCK TC_1 |- typeParameterListOpt : TC_2 typeId_expl*
-- ConstructorParameterListOpt_ok: TC_2 |- constructorParameterListOpt : TC_3 constructorParameterIR* # typeId_impl*
-- ParameterList_ok: BLOCK TC_3 |- parameterList : TC_4 parameterIR* # eps
-- ControlLocalDeclList_ok: TC_4 |- controlLocalDeclarationList : TC_5 controlLocalDeclarationIR*
-- if TC_6 = TC_5[LOCAL.KIND = CONTROL_APPLY_METHOD]
-- Block_ok: TC_6 CONT NOLOOP |- controlBody : _ _ controlBodyIR
-- if callableTypeDefIR = CONTROL_APPLY `(parameterIR*)
-- CallableTypeDef_wf: $bound(BLOCK, TC_2) |- callableTypeDefIR
-- if nameIR = $name(name)
-- if constructorId = $constructorId(name, constructorParameterListOpt)
-- if typeDefIR_control = CONTROL nameIR `<typeId_expl* , typeId_impl*> `(parameterIR*)
-- if typeIR_control = $typeIR_of_typeDefIR(typeDefIR_control)
-- if constructorTypeDefIR = CONSTRUCTOR `<typeId_expl* , typeId_impl*> `(constructorParameterIR*) : typeIR_control
-- ConstructorTypeDef_wf: $bound(GLOBAL, TC_0) |- constructorTypeDefIR
-- if TC_7 = $add_constructorDef_t(TC_0, constructorId, constructorTypeDefIR)
-- if controlDeclarationIR = annotationList CONTROL nameIR `<typeId_expl* , typeId_impl*> `(parameterIR*) `(constructorParameterIR*) `{controlLocalDeclarationIR* APPLY controlBodyIR}
11.12.2. Compile-time evaluation
At compile-time, control declarations are loaded into the context as control object constructors.
Click to view the specification source
rulegroup Decl_inst/controlDeclarationIR:
rule Decl_inst/controlDeclarationIR:
IC_0 STO |- annotationList CONTROL nameIR `<typeParameterListIR_expl , typeParameterListIR_impl> `(parameterListIR) `(constructorParameterListIR) `{controlLocalDeclarationListIR APPLY controlBodyIR} : IC_1 STO
-- if callableId = $callableId_IR(nameIR, constructorParameterListIR)
-- if typeParameterListIR = typeParameterListIR_expl ++ typeParameterListIR_impl
-- if constructorDef = CONTROL `<typeParameterListIR> `(parameterListIR) `(constructorParameterListIR) `{controlLocalDeclarationListIR APPLY controlBodyIR}
-- if IC_1 = $add_constructorDef_i(IC_0, callableId, constructorDef)
When instantiated, an instance of a control declaration is created:
controlObject
: CONTROL `< theta > `( parameterListIR )
`{ frame controlLocalDeclarationListIR actionDefEnv controlBodyIR }
;
See Section 10.5 for details about control objects.
11.13. Enum type declaration
An enumeration type is defined using the following syntax:
enumTypeDeclaration
: annotationList ENUM name `{ nameList trailingCommaOpt }
| annotationList ENUM type name `{ namedExpressionList trailingCommaOpt }
;
namedExpressionList
: namedExpression
| namedExpressionList , namedExpression
;
namedExpression
: name = expression
;
After type checking, enum type declarations are represented as follows:
enumTypeDeclarationIR
: annotationList ENUM nameIR `{ nameListIR }
| annotationList ENUM typeIR nameIR `{ namedValueListIR }
;
namedValueListIR = namedValueIR*
namedValueIR
: nameIR = value
;
See Section 8.4.8 for more details on enums.
11.13.1. Enum type declarations without an underyling type
11.13.1.1. Type checking
Click to view the specification source
rulegroup Decl_ok/enumTypeDeclaration-non-serializable:
rule Decl_ok/enumTypeDeclaration-non-serializable:
TC_0 |- annotationList ENUM name `{nameList_field _} : TC_2 enumTypeDeclarationIR
-- if nameIR = $name(name)
-- if name_field* = $flatten_nameList(nameList_field)
-- (if (nameIR_field = $name(name_field)))*
-- if enumTypeDefIR = ENUM nameIR `{nameIR_field*}
-- TypeDef_wf: $bound(GLOBAL, TC_0) |- enumTypeDefIR
-- if TC_1 = $add_typeDef_t(GLOBAL, TC_0, nameIR, enumTypeDefIR)
-- (if (id_field = nameIR ++ "." ++ nameIR_field))*
-- (if (value_field = nameIR . nameIR_field))*
-- (if (varTypeIR = `EMPTY enumTypeDefIR LCTK value_field))*
-- if TC_2 = $add_vars_t(GLOBAL, TC_1, id_field*, varTypeIR*)
-- if enumTypeDeclarationIR = annotationList ENUM nameIR `{nameIR_field*}
11.13.1.2. Compile-time evaluation
At compile-time, enum type declarations are loaded into the context by:
Click to view the specification source
rulegroup Decl_inst/enumTypeDeclarationIR-non-serializable:
rule Decl_inst/enumTypeDeclarationIR-non-serializable:
IC_0 STO |- annotationList ENUM nameIR `{nameIR_field*} : IC_2 STO
-- (if (nameIR_enum_field = nameIR ++ "." ++ nameIR_field))*
-- (if (value_enum_field = nameIR . nameIR_field))*
-- if IC_1 = $add_vars_i(GLOBAL, IC_0, nameIR_enum_field*, value_enum_field*)
-- if enumTypeDefIR = ENUM nameIR `{nameIR_field*}
-- if IC_2 = $add_typeDef_i(GLOBAL, IC_1, nameIR, enumTypeDefIR)
11.13.2. Enum type declarations with an underlying type
11.13.2.1. Type checking
Click to view the specification source
rulegroup Decl_ok/enumTypeDeclaration-serializable:
rule Decl_ok/enumTypeDeclaration-serializable:
TC_0 |- annotationList ENUM type name `{namedExpressionList_field _} : TC_3 enumTypeDeclarationIR
-- if B = $bound(GLOBAL, TC_0)
-- Type_ok: GLOBAL TC_0 |- type : typeIR # eps
-- Type_wf: B |- typeIR
-- if nameIR = $name(name)
-- Enum_serializable_fieldList_ok: TC_0 nameIR typeIR |- namedExpressionList_field : TC_1 namedValueIR_field*
-- (if ((nameIR_field = value_field) = namedValueIR_field))*
-- (if (id_field = nameIR ++ "." ++ nameIR_field))*
-- if enumTypeDefIR = ENUM nameIR `<typeIR> `{(nameIR_field = value_field ;)*}
-- (if (varTypeIR = `EMPTY enumTypeDefIR LCTK value_field))*
-- if TC_2 = $add_vars_t(GLOBAL, TC_0, id_field*, varTypeIR*)
-- TypeDef_wf: B |- enumTypeDefIR
-- if TC_3 = $add_typeDef_t(GLOBAL, TC_2, nameIR, enumTypeDefIR)
-- if enumTypeDeclarationIR = annotationList ENUM typeIR nameIR `{namedValueIR_field*}
An enum field is type-checked as follows:
Click to view the specification source
relation Enum_serializable_field_ok: typingContext nameIR typeIR |- namedExpression : typingContext namedValueIR
Click to view the specification source
rulegroup Enum_serializable_field_ok: rule Enum_serializable_field_ok: TC_0 nameIR_enum typeIR |- name = expression : TC_1 (nameIR = value) -- Expr_ok: BLOCK TC_0 |- expression : typedExpressionIR -- if typedExpressionIR_cast = $cast_unary(typedExpressionIR, typeIR) -- if LCTK = $ctk_of_typedExpressionIR(typedExpressionIR_cast) -- Expr_eval_lctk: BLOCK TC_0 |- typedExpressionIR_cast ~> value -- if nameIR = $name(name) -- if varTypeIR = `EMPTY typeIR LCTK value -- if TC_1 = $add_var_t(BLOCK, TC_0, nameIR, varTypeIR)
A list of enum fields is type-checked with the following relation:
Click to view the specification source
relation Enum_serializable_fieldList_ok: typingContext nameIR typeIR |- namedExpressionList : typingContext namedValueIR*
11.13.2.2. Compile-time evaluation
At compile-time, enum type declarations are loaded into the context by:
Click to view the specification source
rulegroup Decl_inst/enumTypeDeclarationIR-serializable:
rule Decl_inst/enumTypeDeclarationIR-serializable:
IC_0 STO |- annotationList ENUM typeIR nameIR `{(nameIR_field = value_field)*} : IC_2 STO
-- (if (nameIR_serenum_field = nameIR ++ "." ++ nameIR_field))*
-- (if (value_serenum_field = nameIR . nameIR_field . value_field))*
-- if IC_1 = $add_vars_i(GLOBAL, IC_0, nameIR_serenum_field*, value_serenum_field*)
-- if enumTypeDefIR = ENUM nameIR `<typeIR> `{(nameIR_field = value_field ;)*}
-- if IC_2 = $add_typeDef_i(GLOBAL, IC_1, nameIR, enumTypeDefIR)
11.14. Struct type declaration
A struct type is defined with the following syntax:
structTypeDeclaration
: annotationList STRUCT name typeParameterListOpt `{ typeFieldList }
;
typeFieldList
: /* empty */
| typeFieldList typeField
;
typeField
: annotationList type name ;
;
See Section 8.4.5 for more information about struct types.
11.14.1. Type checking
After type checking, a struct type declaration is represented in P4IR as:
structTypeDeclarationIR
: annotationList STRUCT nameIR `< typeParameterListIR > `{ typeFieldListIR }
;
typeFieldListIR = typeFieldIR*
typeFieldIR
: annotationList typeIR nameIR ;
;
Click to view the specification source
rulegroup Decl_ok/structTypeDeclaration:
rule Decl_ok/structTypeDeclaration:
TC_0 |- annotationList STRUCT name typeParameterListOpt `{typeFieldList} : TC_2 structTypeDeclarationIR
-- TypeParameterListOpt_ok: BLOCK TC_0 |- typeParameterListOpt : TC_1 typeId*
-- if (annotationList_field type_field name_field ;)* = $flatten_typeFieldList(typeFieldList)
-- (Type_ok: BLOCK TC_1 |- type_field : typeIR_field # eps)*
-- if nameIR = $name(name)
-- (if (nameIR_field = $name(name_field)))*
-- if structTypeDefIR = STRUCT nameIR `<typeId*> `{(annotationList_field typeIR_field nameIR_field ;)*}
-- TypeDef_wf: $bound(GLOBAL, TC_0) |- structTypeDefIR
-- if TC_2 = $add_typeDef_t(GLOBAL, TC_0, nameIR, structTypeDefIR)
-- if structTypeDeclarationIR = annotationList STRUCT nameIR `<typeId*> `{(annotationList_field typeIR_field nameIR_field ;)*}
11.14.2. Compile-time evaluation
At compile-time, struct type declarations are loaded into the context by:
Click to view the specification source
rulegroup Decl_inst/structTypeDeclarationIR:
rule Decl_inst/structTypeDeclarationIR:
IC_0 STO |- annotationList STRUCT nameIR `<typeParameterListIR> `{typeFieldIR*} : IC_1 STO
-- (if (annotationList_field typeIR_field nameIR_field ; = typeFieldIR))*
-- if structTypeDefIR = STRUCT nameIR `<typeParameterListIR> `{(annotationList_field typeIR_field nameIR_field ;)*}
-- if IC_1 = $add_typeDef_i(GLOBAL, IC_0, nameIR, structTypeDefIR)
11.15. Header type declaration
A header type is defined with the following syntax:
headerTypeDeclaration
: annotationList HEADER name typeParameterListOpt `{ typeFieldList }
;
typeFieldList
: /* empty */
| typeFieldList typeField
;
typeField
: annotationList type name ;
;
See Section 8.4.6 for more information about header types.
11.15.1. Type checking
After type checking, a header type declaration is represented in P4IR as:
headerTypeDeclarationIR
: annotationList HEADER nameIR `< typeParameterListIR > `{ typeFieldListIR }
;
typeFieldListIR = typeFieldIR*
typeFieldIR
: annotationList typeIR nameIR ;
;
Click to view the specification source
rulegroup Decl_ok/headerTypeDeclaration:
rule Decl_ok/headerTypeDeclaration:
TC_0 |- annotationList HEADER name typeParameterListOpt `{typeFieldList} : TC_2 headerTypeDeclarationIR
-- TypeParameterListOpt_ok: BLOCK TC_0 |- typeParameterListOpt : TC_1 typeId*
-- if (annotationList_f type_f name_f ;)* = $flatten_typeFieldList(typeFieldList)
-- (Type_ok: BLOCK TC_1 |- type_f : typeIR_f # eps)*
-- if nameIR = $name(name)
-- (if (nameIR_f = $name(name_f)))*
-- if headerTypeDefIR = HEADER nameIR `<typeId*> `{(annotationList_f typeIR_f nameIR_f ;)*}
-- TypeDef_wf: $bound(GLOBAL, TC_0) |- headerTypeDefIR
-- if TC_2 = $add_typeDef_t(GLOBAL, TC_0, nameIR, headerTypeDefIR)
-- if headerTypeDeclarationIR = annotationList HEADER nameIR `<typeId*> `{(annotationList_f typeIR_f nameIR_f ;)*}
11.15.2. Compile-time evaluation
At compile-time, header type declarations are loaded into the context by:
Click to view the specification source
rulegroup Decl_inst/headerTypeDeclarationIR:
rule Decl_inst/headerTypeDeclarationIR:
IC_0 STO |- annotationList HEADER nameIR `<typeParameterListIR> `{typeFieldIR*} : IC_1 STO
-- (if (annotationList_field typeIR_field nameIR_field ; = typeFieldIR))*
-- if headerTypeDefIR = HEADER nameIR `<typeParameterListIR> `{(annotationList_field typeIR_field nameIR_field ;)*}
-- if IC_1 = $add_typeDef_i(GLOBAL, IC_0, nameIR, headerTypeDefIR)
11.16. Header union type declaration
A header union type is defined with the following syntax:
headerUnionTypeDeclaration
: annotationList HEADER_UNION name typeParameterListOpt `{ typeFieldList }
;
typeFieldList
: /* empty */
| typeFieldList typeField
;
typeField
: annotationList type name ;
;
See Section 8.4.7 for more information about header union types.
11.16.1. Type checking
After type checking, a header union type declaration is represented in P4IR as:
headerUnionTypeDeclarationIR
: annotationList HEADER_UNION nameIR `< typeParameterListIR >
`{ typeFieldListIR }
;
typeFieldListIR = typeFieldIR*
typeFieldIR
: annotationList typeIR nameIR ;
;
Click to view the specification source
rulegroup Decl_ok/headerUnionTypeDeclaration:
rule Decl_ok/headerUnionTypeDeclaration:
TC_0 |- annotationList HEADER_UNION name typeParameterListOpt `{typeFieldList} : TC_2 headerUnionTypeDeclarationIR
-- TypeParameterListOpt_ok: BLOCK TC_0 |- typeParameterListOpt : TC_1 typeId*
-- if (annotationList_f type_f name_f ;)* = $flatten_typeFieldList(typeFieldList)
-- (Type_ok: BLOCK TC_1 |- type_f : typeIR_f # eps)*
-- if nameIR = $name(name)
-- (if (nameIR_f = $name(name_f)))*
-- if headerUnionTypeDefIR = HEADER_UNION nameIR `<typeId*> `{(annotationList_f typeIR_f nameIR_f ;)*}
-- TypeDef_wf: $bound(GLOBAL, TC_0) |- headerUnionTypeDefIR
-- if TC_2 = $add_typeDef_t(GLOBAL, TC_0, nameIR, headerUnionTypeDefIR)
-- if headerUnionTypeDeclarationIR = annotationList HEADER_UNION nameIR `<typeId*> `{(annotationList_f typeIR_f nameIR_f ;)*}
11.16.2. Compile-time evaluation
At compile-time, header union type declarations are loaded into the context by:
Click to view the specification source
rulegroup Decl_inst/headerUnionTypeDeclarationIR:
rule Decl_inst/headerUnionTypeDeclarationIR:
IC_0 STO |- annotationList HEADER_UNION nameIR `<typeParameterListIR> `{typeFieldIR*} : IC_1 STO
-- (if (annotationList_field typeIR_field nameIR_field ; = typeFieldIR))*
-- if headerUnionTypeDefIR = HEADER_UNION nameIR `<typeParameterListIR> `{(annotationList_field typeIR_field nameIR_field ;)*}
-- if IC_1 = $add_typeDef_i(GLOBAL, IC_0, nameIR, headerUnionTypeDefIR)
11.17. Typedef declaration
A typedef or type declaration introduces an alias for an existing type.
typedef
: type
| derivedTypeDeclaration
;
typedefDeclaration
: annotationList TYPEDEF typedef name ;
| annotationList TYPE type name ;
;
In P4IR, typedef and type declarations are represented as:
typedefIR
: typeIR
| derivedTypeDeclarationIR
;
typedefDeclarationIR
: annotationList TYPEDEF typedefIR nameIR ;
| annotationList TYPE typeIR nameIR ;
;
11.17.1. typedef declaration
See Section 8.5.7 for more details about typedef aliases.
11.17.1.1. Type checking
Click to view the specification source
rulegroup Decl_ok/typedefDeclaration-typedef:
rule Decl_ok/type:
TC_0 |- annotationList TYPEDEF type name ; : TC_1 typedefDeclarationIR
-- if B = $bound(GLOBAL, TC_0)
-- Type_ok: GLOBAL TC_0 |- type : typeIR # eps
-- Type_wf: B |- typeIR
-- if nameIR = $name(name)
-- if typeDefIR_typedef = TYPEDEF nameIR typeIR
-- TypeDef_wf: B |- typeDefIR_typedef
-- if TC_1 = $add_typeDef_t(GLOBAL, TC_0, nameIR, typeDefIR_typedef)
-- if typedefDeclarationIR = annotationList TYPEDEF typeIR nameIR ;
rule Decl_ok/derivedTypeDeclaration-mono:
TC_0 |- annotationList TYPEDEF derivedTypeDeclaration name ; : TC_1 typedefDeclarationIR
-- Decl_ok: TC_0 |- derivedTypeDeclaration : TC_1 derivedTypeDeclarationIR
-- if `{typeId} = $diff_set<typeId>($dom_map<typeId, typeDefIR>(TC_1.GLOBAL.TYPE), $dom_map<typeId, typeDefIR>(TC_0.GLOBAL.TYPE))
-- if typeDefIR = $find_typeDef_t(GLOBAL, TC_1, ` typeId)
-- if $is_monomorphic_typeDefIR(typeDefIR)
-- if typeIR = $typeIR_of_typeDefIR(typeDefIR)
-- if nameIR = $name(name)
-- if typedefTypeIR = TYPEDEF nameIR typeIR
-- TypeDef_wf: $bound(GLOBAL, TC_0) |- typedefTypeIR
-- if TC_2 = $add_typeDef_t(GLOBAL, TC_0, nameIR, typedefTypeIR)
-- if typedefDeclarationIR = annotationList TYPEDEF derivedTypeDeclarationIR nameIR ;
rule Decl_ok/derivedTypeDeclaration-poly:
TC_0 |- annotationList TYPEDEF derivedTypeDeclaration name ; : TC_1 typedefDeclarationIR
-- Decl_ok: TC_0 |- derivedTypeDeclaration : TC_1 derivedTypeDeclarationIR
-- if `{typeId} = $diff_set<typeId>($dom_map<typeId, typeDefIR>(TC_1.GLOBAL.TYPE), $dom_map<typeId, typeDefIR>(TC_0.GLOBAL.TYPE))
-- if typeDefIR = $find_typeDef_t(GLOBAL, TC_1, ` typeId)
-- if ~$is_monomorphic_typeDefIR(typeDefIR)
-- if (eps, eps) = $typeParameterListIR_of_typeDefIR(typeDefIR)
-- if nameIR = $name(name)
-- if typeIR = $specialize_typeDefIR(typeDefIR, eps)
-- if typedefTypeIR = TYPEDEF nameIR typeIR
-- TypeDef_wf: $bound(GLOBAL, TC_0) |- typedefTypeIR
-- if TC_2 = $add_typeDef_t(GLOBAL, TC_0, nameIR, typedefTypeIR)
-- if typedefDeclarationIR = annotationList TYPEDEF derivedTypeDeclarationIR nameIR ;
11.17.1.2. Compile-time evaluation
At compile-time, typedefs are loaded into the context with:
Click to view the specification source
rulegroup Decl_inst/typedefDeclarationIR-typedef:
rule Decl_inst/typeIR:
IC_0 STO |- annotationList TYPEDEF typeIR nameIR ; : IC_1 STO
-- if aliasTypeDefIR = TYPEDEF nameIR typeIR
-- if IC_1 = $add_typeDef_i(GLOBAL, IC_0, nameIR, aliasTypeDefIR)
rule Decl_inst/derivedTypeDeclarationIR-monomorphic:
IC_0 STO |- annotationList TYPEDEF derivedTypeDeclarationIR nameIR ; : IC_1 STO
-- Decl_inst: IC_0 $empty_store |- derivedTypeDeclarationIR : IC_local _
-- if `{typeId_newtype} = $diff_set<typeId>($dom_map<typeId, typeDefIR>(IC_local.GLOBAL.TYPE), $dom_map<typeId, typeDefIR>(IC_0.GLOBAL.TYPE))
-- if typeDefIR = $find_typeDef_i(GLOBAL, IC_local, . typeId_newtype)
-- if $is_monomorphic_typeDefIR(typeDefIR)
-- if typeIR = $typeIR_of_typeDefIR(typeDefIR)
-- if aliasTypeDefIR = TYPEDEF nameIR typeIR
-- if IC_1 = $add_typeDef_i(GLOBAL, IC_0, nameIR, aliasTypeDefIR)
rule Decl_inst/derivedTypeDeclaration-poly:
IC_0 STO |- annotationList TYPEDEF derivedTypeDeclarationIR nameIR ; : IC_1 STO
-- Decl_inst: IC_0 $empty_store |- derivedTypeDeclarationIR : IC_local _
-- if `{typeId_newtype} = $diff_set<typeId>($dom_map<typeId, typeDefIR>(IC_local.GLOBAL.TYPE), $dom_map<typeId, typeDefIR>(IC_0.GLOBAL.TYPE))
-- if typeDefIR = $find_typeDef_i(GLOBAL, IC_local, . typeId_newtype)
-- if ~$is_monomorphic_typeDefIR(typeDefIR)
-- if (eps, eps) = $typeParameterListIR_of_typeDefIR(typeDefIR)
-- if typeIR = $specialize_typeDefIR(typeDefIR, eps)
-- if typedefTypeIR = TYPEDEF nameIR typeIR
-- if IC_1 = $add_typeDef_i(GLOBAL, IC_0, nameIR, typedefTypeIR)
11.17.2. New type declaration
See Section 8.5.8 for more details about new type aliases.
11.17.2.1. Type checking
Click to view the specification source
rulegroup Decl_ok/typedefDeclaration-newtype: rule Decl_ok/typedefDeclaration-newtype: TC_0 |- annotationList TYPE type name ; : TC_1 typedefDeclarationIR -- if B = $bound(GLOBAL, TC_0) -- Type_ok: GLOBAL TC_0 |- type : typeIR # eps -- Type_wf: B |- typeIR -- if nameIR = $name(name) -- if newTypeIR = TYPE nameIR typeIR -- TypeDef_wf: B |- newTypeIR -- if TC_1 = $add_typeDef_t(GLOBAL, TC_0, nameIR, newTypeIR) -- if typedefDeclarationIR = annotationList TYPE typeIR nameIR ;
11.17.2.2. Compile-time evaluation
At compile-time, types are loaded into the context with:
Click to view the specification source
rulegroup Decl_inst/typedefDeclarationIR-newtype: rule Decl_inst/typedefDeclarationIR-newtype: IC_0 STO |- annotationList TYPE typeIR nameIR ; : IC_1 STO -- if aliasTypeDefIR = TYPE nameIR typeIR -- if IC_1 = $add_typeDef_i(GLOBAL, IC_0, nameIR, aliasTypeDefIR)
11.18. Parser type declaration
A parser type declaration introduces a new parser type.
parserTypeDeclaration
: annotationList PARSER name typeParameterListOpt `( parameterList ) ;
;
See Section 8.5.2 for more details on parser object types.
11.18.1. Type checking
After type checking, a parser type declaration has the following form:
parserTypeDeclarationIR
: annotationList PARSER nameIR `< typeParameterListIR , typeParameterListIR >
`( parameterListIR ) ;
;
Click to view the specification source
rulegroup Decl_ok/parserTypeDeclaration: rule Decl_ok/parserTypeDeclaration: TC_0 |- annotationList PARSER name typeParameterListOpt `(parameterList) ; : TC_4 parserTypeDeclarationIR -- if TC_1 = TC_0[BLOCK.KIND = PARSER] -- TypeParameterListOpt_ok: BLOCK TC_1 |- typeParameterListOpt : TC_2 typeId_expl* -- ParameterList_ok: BLOCK TC_2 |- parameterList : TC_3 parameterIR* # typeId_impl* -- if nameIR = $name(name) -- if parserObjectTypeDefIR = PARSER nameIR `<typeId_expl* , typeId_impl*> `(parameterIR*) -- TypeDef_wf: $bound(GLOBAL, TC_0) |- parserObjectTypeDefIR -- if TC_4 = $add_typeDef_t(GLOBAL, TC_0, nameIR, parserObjectTypeDefIR) -- if parserTypeDeclarationIR = annotationList PARSER nameIR `<typeId_expl* , typeId_impl*> `(parameterIR*) ;
11.18.2. Compile-time evaluation
At compile-time, a parser type declaration is loaded into the context with:
Click to view the specification source
rulegroup Decl_inst/parserTypeDeclarationIR: rule Decl_inst/parserTypeDeclarationIR: IC_0 STO |- annotationList PARSER nameIR `<typeParameterListIR , typeParameterListIR_inferred> `(parameterIR*) ; : IC_1 STO -- if parserObjectTypeDefIR = PARSER nameIR `<typeParameterListIR , typeParameterListIR_inferred> `(parameterIR*) -- if IC_1 = $add_typeDef_i(GLOBAL, IC_0, nameIR, parserObjectTypeDefIR)
11.19. Control type declaration
A control type declaration introduces a new control type.
controlTypeDeclaration
: annotationList CONTROL name typeParameterListOpt `( parameterList ) ;
;
See Section 8.5.3 for more details on control object types.
11.19.1. Type checking
After type checking, a control type declaration has the following form:
controlTypeDeclarationIR
: annotationList CONTROL nameIR
`< typeParameterListIR , typeParameterListIR > `( parameterListIR ) ;
;
Click to view the specification source
rulegroup Decl_ok/controlTypeDeclaration: rule Decl_ok/controlTypeDeclaration: TC_0 |- annotationList CONTROL name typeParameterListOpt `(parameterList) ; : TC_4 controlTypeDeclarationIR -- if TC_1 = TC_0[BLOCK.KIND = CONTROL] -- TypeParameterListOpt_ok: BLOCK TC_1 |- typeParameterListOpt : TC_2 typeId_expl* -- ParameterList_ok: BLOCK TC_2 |- parameterList : TC_3 parameterIR* # typeId_impl* -- if nameIR = $name(name) -- if controlObjectTypeDefIR = CONTROL nameIR `<typeId_expl* , typeId_impl*> `(parameterIR*) -- TypeDef_wf: $bound(GLOBAL, TC_0) |- controlObjectTypeDefIR -- if TC_4 = $add_typeDef_t(GLOBAL, TC_0, nameIR, controlObjectTypeDefIR) -- if controlTypeDeclarationIR = annotationList CONTROL nameIR `<typeId_expl* , typeId_impl*> `(parameterIR*) ;
11.19.2. Compile-time evaluation
At compile-time, a control type declaration is loaded into the context with:
Click to view the specification source
rulegroup Decl_inst/controlTypeDeclarationIR: rule Decl_inst/controlTypeDeclarationIR: IC_0 STO |- annotationList CONTROL nameIR `<typeParameterListIR , typeParameterListIR_inferred> `(parameterIR*) ; : IC_1 STO -- if controlObjectTypeDefIR = CONTROL nameIR `<typeParameterListIR , typeParameterListIR_inferred> `(parameterIR*) -- if IC_1 = $add_typeDef_i(GLOBAL, IC_0, nameIR, controlObjectTypeDefIR)
11.20. Package type declaration
A package type declaration introduces a new package type.
packageTypeDeclaration
: annotationList PACKAGE name typeParameterListOpt `( parameterList ) ;
;
See Section 8.5.4 for more details on package object types.
11.20.1. Type checking
After type checking, a package type declaration has the following form:
packageTypeDeclarationIR
: annotationList PACKAGE nameIR
`< typeParameterListIR , typeParameterListIR > `( parameterListIR ) ;
;
Click to view the specification source
rulegroup Decl_ok/packageTypeDeclaration:
rule Decl_ok/packageTypeDeclaration:
TC_0 |- annotationList PACKAGE name typeParameterListOpt `(parameterList) ; : TC_5 packageTypeDeclarationIR
-- if B = $bound(GLOBAL, TC_0)
-- if TC_1 = TC_0[BLOCK.KIND = PACKAGE]
-- TypeParameterListOpt_ok: BLOCK TC_1 |- typeParameterListOpt : TC_2 typeId_expl*
-- ConstructorParameterListOpt_ok: TC_2 |- `(parameterList) : TC_3 constructorParameterIR* # typeId_impl*
-- if nameIR = $name(name)
-- (if (_ _ typeIR_package_inner _ _ = constructorParameterIR))*
-- if packageObjectTypeDefIR = PACKAGE nameIR `<typeId_expl* , typeId_impl*> `{typeIR_package_inner*}
-- TypeDef_wf: B |- packageObjectTypeDefIR
-- if TC_4 = $add_typeDef_t(GLOBAL, TC_0, nameIR, packageObjectTypeDefIR)
-- if constructorId = $constructorId(name, `(parameterList))
-- if typeIR_package = $typeIR_of_typeDefIR(packageObjectTypeDefIR)
-- if constructorTypeDefIR = CONSTRUCTOR `<typeId_expl* , typeId_impl*> `(constructorParameterIR*) : typeIR_package
-- ConstructorTypeDef_wf: B |- constructorTypeDefIR
-- if TC_5 = $add_constructorDef_t(TC_4, constructorId, constructorTypeDefIR)
-- if packageTypeDeclarationIR = annotationList PACKAGE nameIR `<typeId_expl* , typeId_impl*> `(constructorParameterIR*) ;
11.20.2. Compile-time evaluation
At compile-time, a package type declaration is loaded into the context with:
Click to view the specification source
rulegroup Decl_inst/packageTypeDeclarationIR:
rule Decl_inst/packageTypeDeclarationIR:
IC_0 STO |- annotationList PACKAGE nameIR `<typeParameterListIR , typeParameterListIR_inferred> `(parameterIR*) ; : IC_2 STO
-- (if (_ _ typeIR _ _ = parameterIR))*
-- if packageObjectTypeDefIR = PACKAGE nameIR `<typeParameterListIR , typeParameterListIR_inferred> `{typeIR*}
-- if IC_1 = $add_typeDef_i(GLOBAL, IC_0, nameIR, packageObjectTypeDefIR)
-- if callableId = $callableId_IR(nameIR, parameterIR*)
-- if constructorDef = PACKAGE `<typeParameterListIR ++ typeParameterListIR_inferred> `(parameterIR*)
-- if IC_2 = $add_constructorDef_i(IC_1, callableId, constructorDef)
12. L-values
L-values are expressions that may appear on the left side of an assignment
operation or as arguments corresponding to out and inout function
parameters. An l-value represents a storage reference. The following
expressions are legal l-values:
lvalue
: referenceExpression
| lvalue . member
| lvalue `[ expression ]
| lvalue `[ expression : expression ]
| `( lvalue )
;
-
Identifiers of a base or derived type.
-
Structure, header, and header union field member access operations (using the dot notation).
-
References to elements within header stacks (see Section 8.4.3): indexing, and references to
lastandnext. -
The result of a bit-slice operator
[m:l].
The following is a legal l-value: headers.stack[4].field. Note that method
and function calls cannot return l-values.
12.1. Semantics of l-values
12.1.1. Type checking
After type checking, l-values are represented in P4IR as:
typedLvalueIR
: lvalueIR # lvalueNoteIR
;
lvalueIR
: referenceExpressionIR
| typedLvalueIR . nameIR
| typedLvalueIR `[ typedExpressionIR ]
| typedLvalueIR `[ typedExpressionIR : typedExpressionIR ]
| `( typedLvalueIR )
;
lvalueNoteIR
: `( typeIR )
;
Notice that l-values in P4IR carry type information. L-values are type checked according to the relation:
Click to view the specification source
relation Lvalue_ok: cursor typingContext |- lvalue : typedLvalueIR
The following helper functions are used to fetch the type of a typed l-value:
Click to view the specification source
def $type_of_typedLvalueIR(typedLvalueIR) = typeIR -- if _ # `(typeIR) = typedLvalueIR
12.1.2. Runtime evaluation
At runtime, l-values are represented as storage references:
storageReference
: referenceExpressionIR
| storageReference . nameIR
| storageReference `[ value ]
| storageReference `[ value : value ]
;
12.1.2.1. Creation
The following relation evaluates an l-value to a storage reference:
Click to view the specification source
relation Lvalue_eval: cursor evalContext arch |- typedLvalueIR : evalContext arch storageReferenceResult
The result of evaluating an l-value is:
storageReferenceResult
: continueResult<storageReference?>
| abortResult
;
continueResult<X>
: ` X
;
abortResult
: exitResult
| rejectTransitionResult
;
12.1.2.2. Read
The following relation reads a value from a storage reference:
Click to view the specification source
relation Lvalue_read: cursor evalContext arch |- storageReference : value
12.1.2.3. Write
The following relation writes a value to a storage reference:
Click to view the specification source
relation Lvalue_write: cursor evalContext arch |- storageReference -> value : evalContext
The subsequent sections describe each kind of l-value in detail.
12.2. Identifiers
12.2.1. Type checking
Click to view the specification source
rulegroup Lvalue_ok/referenceExpression: rule Lvalue_ok/referenceExpression: p TC |- prefixedNonTypeName : prefixedNameIR # `(typeIR) -- if prefixedNameIR = $prefixedNonTypeName(prefixedNonTypeName) -- if direction typeIR DYN eps = $find_var_t(prefixedNameIR, p, TC) -- if direction = OUT \/ direction = INOUT
12.2.2. Creation
Click to view the specification source
rulegroup Lvalue_eval/referenceExpressionIR: rule Lvalue_eval/referenceExpressionIR: p EC ARCH |- referenceExpressionIR # _ : EC ARCH (` referenceExpressionIR)
12.2.3. Read
Click to view the specification source
rulegroup Lvalue_read/referenceExpressionIR: rule Lvalue_read/referenceExpressionIR: p EC ARCH |- prefixedNameIR : value -- if value = $find_var_e(prefixedNameIR, p, EC)
12.2.4. Write
Click to view the specification source
rulegroup Lvalue_write/referenceExpressionIR: rule Lvalue_write/referenceExpressionIR: p EC_0 ARCH |- prefixedNameIR -> value : EC_1 -- if EC_1 = $update_var_e(p, EC_0, prefixedNameIR, value)
12.3. Member accesses
12.3.1. Type checking
Click to view the specification source
rulegroup Lvalue_ok/memberAccess:
rule Lvalue_ok/headerStackTypeIR-next-last:
p TC |- lvalue_base . member : typedLvalueIR
-- Lvalue_ok: p TC |- lvalue_base : typedLvalueIR_base
-- if _ # `(typeIR_base) = typedLvalueIR_base
-- if typeIR `[_] = $unroll_typeIR(typeIR_base)
-- if nameIR = $name(member)
-- if nameIR = "next" \/ nameIR = "last"
-- if (p = BLOCK /\ TC.BLOCK.KIND = PARSER) \/ (p = LOCAL /\ TC.LOCAL.KIND = PARSER_STATE)
-- if typedLvalueIR = (typedLvalueIR_base . nameIR) # `(typeIR)
rule Lvalue_ok/structTypeIR:
p TC |- lvalue_base . member : typedLvalueIR
-- Lvalue_ok: p TC |- lvalue_base : typedLvalueIR_base
-- if _ # `(typeIR_base) = typedLvalueIR_base
-- if STRUCT _ `<_> `{(_ typeIR_field nameIR_field ;)*} = $unroll_typeIR(typeIR_base)
-- if nameIR = $name(member)
-- if typeIR = $assoc_<nameIR, typeIR>(nameIR, (nameIR_field, typeIR_field)*)
-- if typedLvalueIR = (typedLvalueIR_base . nameIR) # `(typeIR)
rule Lvalue_ok/headerTypeIR:
p TC |- lvalue_base . member : typedLvalueIR
-- Lvalue_ok: p TC |- lvalue_base : typedLvalueIR_base
-- if _ # `(typeIR_base) = typedLvalueIR_base
-- if HEADER _ `<_> `{(_ typeIR_field nameIR_field ;)*} = $unroll_typeIR(typeIR_base)
-- if nameIR = $name(member)
-- if typeIR = $assoc_<nameIR, typeIR>(nameIR, (nameIR_field, typeIR_field)*)
-- if typedLvalueIR = (typedLvalueIR_base . nameIR) # `(typeIR)
rule Lvalue_ok/headerUnionTypeIR:
p TC |- lvalue_base . member : typedLvalueIR
-- Lvalue_ok: p TC |- lvalue_base : typedLvalueIR_base
-- if _ # `(typeIR_base) = typedLvalueIR_base
-- if HEADER_UNION _ `<_> `{(_ typeIR_field nameIR_field ;)*} = $unroll_typeIR(typeIR_base)
-- if nameIR = $name(member)
-- if typeIR = $assoc_<nameIR, typeIR>(nameIR, (nameIR_field, typeIR_field)*)
-- if typedLvalueIR = (typedLvalueIR_base . nameIR) # `(typeIR)
12.3.2. Creation
Click to view the specification source
rulegroup Lvalue_eval/memberAccess: rule Lvalue_eval/abort: p EC_0 ARCH_0 |- (typedLvalueIR . nameIR) # _ : EC_1 ARCH_1 abortResult -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 abortResult rule Lvalue_eval/stack-next-out-of-bounds: p EC_0 ARCH_0 |- (typedLvalueIR . nameIR) # _ : EC_1 ARCH_1 rejectTransitionResult -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 (` storageReference) -- Lvalue_read: p EC_1 ARCH_1 |- storageReference : headerStackValue -- if HEADER_STACK `[value_element* `(n_idx ; n_size)] = headerStackValue -- if nameIR = "next" -- if n_idx >= n_size -- if rejectTransitionResult = REJECT (ERROR . "StackOutOfBounds") rule Lvalue_eval/stack-next-in-bounds: p EC_0 ARCH_0 |- (typedLvalueIR . nameIR) # _ : EC_1 ARCH_1 storageReferenceResult -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 (` storageReference) -- Lvalue_read: p EC_1 ARCH_1 |- storageReference : headerStackValue -- if HEADER_STACK `[value_element* `(n_idx ; n_size)] = headerStackValue -- if nameIR = "next" -- if n_idx < n_size -- if storageReferenceResult = ` (storageReference . nameIR) rule Lvalue_eval/cont: p EC_0 ARCH_0 |- (typedLvalueIR . nameIR) # _ : EC_1 ARCH_1 storageReferenceResult -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 (` storageReference) -- Lvalue_read: p EC_1 ARCH_1 |- storageReference : value -- if ~(value <: headerStackValue) -- if storageReferenceResult = ` (storageReference . nameIR)
12.3.3. Read
Click to view the specification source
rulegroup Lvalue_read/memberAccess:
rule Lvalue_read/package:
p EC_0 ARCH |- storageReference . nameIR : value
-- Lvalue_read: p EC_0 ARCH |- storageReference : value_base
-- if REF objectId = value_base
-- if PACKAGE `<_> `{frame} = $find_object_e(ARCH, objectId)
-- if value = $find_map<nameIR, value>(frame, nameIR)
rule Lvalue_read/stack-next:
p EC_0 ARCH |- storageReference . nameIR : value
-- Lvalue_read: p EC_0 ARCH |- storageReference : headerStackValue_base
-- if HEADER_STACK `[value_element* `(n_idx ; n_size)] = headerStackValue_base
-- if nameIR = "next"
-- if n_idx < n_size
-- if value = value_element*[n_idx]
rule Lvalue_read/struct:
p EC_0 ARCH |- storageReference . nameIR : value
-- Lvalue_read: p EC_0 ARCH |- storageReference : structValue_base
-- if STRUCT _ `{(value_field nameIR_field ;)*} = structValue_base
-- if value = $assoc_<id, value>(nameIR, (nameIR_field, value_field)*)
rule Lvalue_read/header:
p EC_0 ARCH |- storageReference . nameIR : value
-- Lvalue_read: p EC_0 ARCH |- storageReference : headerValue_base
-- if HEADER _ `{_ ; (value_field nameIR_field ;)*} = headerValue_base
-- if value = $assoc_<id, value>(nameIR, (nameIR_field, value_field)*)
rule Lvalue_read/header-union:
p EC_0 ARCH |- storageReference . nameIR : value
-- Lvalue_read: p EC_0 ARCH |- storageReference : headerUnionValue_base
-- if HEADER_UNION _ `{(value_field nameIR_field ;)*} = headerUnionValue_base
-- if value = $assoc_<id, value>(nameIR, (nameIR_field, value_field)*)
12.3.4. Write
Click to view the specification source
rulegroup Lvalue_write/memberAccess:
rule Lvalue_write/stack-next:
p EC_0 ARCH |- (storageReference . nameIR) -> value : EC_1
-- Lvalue_read: p EC_0 ARCH |- storageReference : headerStackValue_base
-- if HEADER_STACK `[value_element* `(n_idx ; n_size)] = headerStackValue_base
-- if nameIR = "next"
-- if value_element_update* = value_element*[[n_idx] = value]
-- if headerStackValue_base_update = HEADER_STACK `[value_element_update* `(n_idx + 1 ; n_size)]
-- Lvalue_write: p EC_0 ARCH |- storageReference -> headerStackValue_base_update : EC_1
rule Lvalue_write/struct:
p EC_0 ARCH |- (storageReference . nameIR) -> value : EC_1
-- Lvalue_read: p EC_0 ARCH |- storageReference : structValue_base
-- if STRUCT typeId `{fieldValue*} = structValue_base
-- if fieldValue_update* = $update_fieldValue(fieldValue*, nameIR, value)
-- if structValue_base_update = STRUCT typeId `{fieldValue_update*}
-- Lvalue_write: p EC_0 ARCH |- storageReference -> structValue_base_update : EC_1
rule Lvalue_write/header:
p EC_0 ARCH |- (storageReference . nameIR) -> value : EC_1
-- Lvalue_read: p EC_0 ARCH |- storageReference : headerValue_base
-- if HEADER typeId `{b_valid ; fieldValue*} = headerValue_base
-- if fieldValue_update* = $update_fieldValue(fieldValue*, nameIR, value)
-- if headerValue_base_update = HEADER typeId `{b_valid ; fieldValue_update*}
-- Lvalue_write: p EC_0 ARCH |- storageReference -> headerValue_base_update : EC_1
rule Lvalue_write/header-union-keep-valid-header:
p EC_0 ARCH |- (storageReference . nameIR) -> value : EC_1
-- Lvalue_read: p EC_0 ARCH |- storageReference : headerUnionValue_base
-- if HEADER_UNION typeId `{fieldValue*} = headerUnionValue_base
-- if (value_field nameIR_field ;)* = fieldValue*
-- if value_old = $assoc_<nameIR, value>(nameIR, (nameIR_field, value_field)*)
-- if HEADER _ `{b_valid_old ; _} = value_old
-- if fieldValue_update* = $update_fieldValue(fieldValue*, nameIR, value)
-- if HEADER _ `{b_valid_new ; _} = value
-- if b_toggled = ((~b_valid_old /\ b_valid_new) \/ (b_valid_old /\ ~b_valid_new))
-- if ~b_toggled
-- if headerUnionValue_base_update = HEADER_UNION typeId `{fieldValue_update*}
-- Lvalue_write: p EC_0 ARCH |- storageReference -> headerUnionValue_base_update : EC_1
rule Lvalue_write/header-union-replace-valid-header:
p EC_0 ARCH |- (storageReference . nameIR) -> value : EC_1
-- Lvalue_read: p EC_0 ARCH |- storageReference : headerUnionValue_base
-- if HEADER_UNION typeId `{fieldValue*} = headerUnionValue_base
-- if (value_field nameIR_field ;)* = fieldValue*
-- if value_old = $assoc_<nameIR, value>(nameIR, (nameIR_field, value_field)*)
-- if HEADER _ `{b_valid_old ; _} = value_old
-- if fieldValue_update* = $update_fieldValue(fieldValue*, nameIR, value)
-- if HEADER _ `{b_valid_new ; _} = value
-- if b_toggled = ((~b_valid_old /\ b_valid_new) \/ (b_valid_old /\ ~b_valid_new))
-- if b_toggled
-- if fieldValue_update'* = $update_headerUnion(fieldValue_update*, nameIR)
-- if headerUnionValue_base_update = HEADER_UNION typeId `{fieldValue_update'*}
-- Lvalue_write: p EC_0 ARCH |- storageReference -> headerUnionValue_base_update : EC_1
12.4. Index accesses
12.4.1. Type checking
Click to view the specification source
rulegroup Lvalue_ok/indexAccess: rule Lvalue_ok/lctk: p TC |- lvalue_base `[expression_index] : typedLvalueIR -- Lvalue_ok: p TC |- lvalue_base : typedLvalueIR_base -- if _ # `(typeIR_base) = typedLvalueIR_base -- if typeIR `[n_size] = $unroll_typeIR(typeIR_base) -- Expr_ok: p TC |- expression_index : typedExpressionIR_index -- if _ # `(typeIR_index ctk_index) = typedExpressionIR_index -- if typedExpressionIR_index_reduced = $reduce_serenum_unary(typedExpressionIR_index, $compat_array_index) -- if ctk_index = LCTK -- Expr_eval_lctk: p TC |- typedExpressionIR_index_reduced ~> integerValue_index -- if n_index = $nat_of_integerValue(integerValue_index) -- if n_index < n_size -- if typedLvalueIR = (typedLvalueIR_base `[typedExpressionIR_index_reduced]) # `(typeIR) rule Lvalue_ok/non-lctk: p TC |- lvalue_base `[expression_index] : typedLvalueIR -- Lvalue_ok: p TC |- lvalue_base : typedLvalueIR_base -- if _ # `(typeIR_base) = typedLvalueIR_base -- if typeIR `[n_size] = $unroll_typeIR(typeIR_base) -- Expr_ok: p TC |- expression_index : typedExpressionIR_index -- if _ # `(typeIR_index ctk_index) = typedExpressionIR_index -- if typedExpressionIR_index_reduced = $reduce_serenum_unary(typedExpressionIR_index, $compat_array_index) -- if ctk_index =/= LCTK -- if typedLvalueIR = (typedLvalueIR_base `[typedExpressionIR_index_reduced]) # `(typeIR)
12.4.2. Creation
Click to view the specification source
rulegroup Lvalue_eval/indexAccess: rule Lvalue_eval/base-abort: p EC_0 ARCH_0 |- (typedLvalueIR `[typedExpressionIR]) # _ : EC_1 ARCH_1 abortResult -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 abortResult rule Lvalue_eval/base-cont-index-abort: p EC_0 ARCH_0 |- (typedLvalueIR `[typedExpressionIR]) # _ : EC_2 ARCH_2 abortResult -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 (` storageReference) -- Expr_eval: p EC_1 ARCH_1 |- typedExpressionIR : EC_2 ARCH_2 abortResult rule Lvalue_eval/base-cont-index-cont: p EC_0 ARCH_0 |- (typedLvalueIR `[typedExpressionIR]) # _ : EC_2 ARCH_2 storageReferenceResult -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 (` storageReference) -- Expr_eval: p EC_1 ARCH_1 |- typedExpressionIR : EC_2 ARCH_2 (` integerValue_index) -- if storageReferenceResult = ` (storageReference `[integerValue_index])
12.4.3. Read
Click to view the specification source
rulegroup Lvalue_read/indexAccess:
rule Lvalue_read/stack-in-bounds:
p EC_0 ARCH |- storageReference `[integerValue_index] : value
-- Lvalue_read: p EC_0 ARCH |- storageReference : headerStackValue_base
-- if HEADER_STACK `[value_element* `(_ ; n_size)] = headerStackValue_base
-- if n_idx = $nat_of_integerValue(integerValue_index)
-- if n_idx < n_size
-- if value = value_element*[n_idx]
rule Lvalue_read/stack-out-of-bounds:
p EC_0 ARCH |- storageReference `[integerValue_index] : value
-- Lvalue_read: p EC_0 ARCH |- storageReference : headerStackValue_base
-- if HEADER_STACK `[value_element* `(_ ; n_size)] = headerStackValue_base
-- if n_idx = $nat_of_integerValue(integerValue_index)
-- if n_idx >= n_size
-- if HEADER typeId `{b_valid ; fieldValue*} = value_element*[0]
-- if value = HEADER typeId `{false ; fieldValue*}
12.4.4. Write
Click to view the specification source
rulegroup Lvalue_write/indexAccess: rule Lvalue_write/stack-in-bounds: p EC_0 ARCH |- (storageReference `[integerValue_index]) -> value : EC_1 -- Lvalue_read: p EC_0 ARCH |- storageReference : headerStackValue_base -- if HEADER_STACK `[value_element* `(n_base_idx ; n_size)] = headerStackValue_base -- if n_idx = $nat_of_integerValue(integerValue_index) -- if n_idx < n_size -- if value_element_update* = value_element*[[n_idx] = value] -- if headerStackValue_base_update = HEADER_STACK `[value_element_update* `(n_base_idx ; n_size)] -- Lvalue_write: p EC_0 ARCH |- storageReference -> headerStackValue_base_update : EC_1 rule Lvalue_write/stack-out-of-bounds: p EC_0 ARCH |- (storageReference `[integerValue_index]) -> value : EC_0 -- Lvalue_read: p EC_0 ARCH |- storageReference : headerStackValue_base -- if HEADER_STACK `[value_element* `(_ ; n_size)] = headerStackValue_base -- if n_idx = $nat_of_integerValue(integerValue_index) -- if n_idx >= n_size
12.5. Bit-slice accesses
12.5.1. Type checking
Click to view the specification source
rulegroup Lvalue_ok/sliceAccess: rule Lvalue_ok/sliceAccess: p TC |- lvalue_base `[expression_hi : expression_lo] : typedLvalueIR -- Lvalue_ok: p TC |- lvalue_base : typedLvalueIR_base -- if _ # `(typeIR_base) = typedLvalueIR_base -- if $compat_bitslice_base(typeIR_base) -- Expr_ok: p TC |- expression_hi : typedExpressionIR_hi -- Expr_ok: p TC |- expression_lo : typedExpressionIR_lo -- if typedExpressionIR_hi_reduced = $reduce_serenum_unary(typedExpressionIR_hi, $compat_bitslice_index) -- if typedExpressionIR_lo_reduced = $reduce_serenum_unary(typedExpressionIR_lo, $compat_bitslice_index) -- if _ # `(typeIR_hi_reduced ctk_hi_reduced) = typedExpressionIR_hi_reduced -- if _ # `(typeIR_lo_reduced ctk_lo_reduced) = typedExpressionIR_lo_reduced -- if ctk_hi_reduced = LCTK -- Expr_eval_lctk: p TC |- typedExpressionIR_hi_reduced ~> integerValue_hi -- if n_hi = $nat_of_integerValue(integerValue_hi) -- if ctk_lo_reduced = LCTK -- Expr_eval_lctk: p TC |- typedExpressionIR_lo_reduced ~> integerValue_lo -- if n_lo = $nat_of_integerValue(integerValue_lo) -- if $is_valid_bitslice(typeIR_base, n_lo, n_hi) -- if n_slice = n_hi - n_lo + 1 -- if typeIR = BIT `<n_slice> -- if typedLvalueIR = (typedLvalueIR_base `[typedExpressionIR_hi_reduced : typedExpressionIR_lo_reduced]) # `(typeIR)
12.5.2. Creation
Click to view the specification source
rulegroup Lvalue_eval/sliceAccess: rule Lvalue_eval/abort: p EC_0 ARCH_0 |- (typedLvalueIR `[typedExpressionIR_hi : typedExpressionIR_lo]) # _ : EC_1 ARCH_1 abortResult -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 abortResult rule Lvalue_eval/cont-abort: p EC_0 ARCH_0 |- (typedLvalueIR `[typedExpressionIR_hi : typedExpressionIR_lo]) # _ : EC_2 ARCH_2 abortResult -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 (` storageReference) -- Exprs_eval: p EC_1 ARCH_1 |- [typedExpressionIR_hi, typedExpressionIR_lo] : EC_2 ARCH_2 abortResult rule Lvalue_eval/cont: p EC_0 ARCH_0 |- (typedLvalueIR `[typedExpressionIR_hi : typedExpressionIR_lo]) # _ : EC_1 ARCH_1 storageReferenceResult -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 (` storageReference) -- Exprs_eval: p EC_1 ARCH_1 |- [typedExpressionIR_hi, typedExpressionIR_lo] : EC_2 ARCH_2 (` ([value_hi, value_lo])) -- if storageReferenceResult = ` (storageReference `[value_hi : value_lo])
12.5.3. Read
Click to view the specification source
rulegroup Lvalue_read/sliceAccess: rule Lvalue_read/sliceAccess: p EC_0 ARCH |- (storageReference `[value_hi : value_lo]) : value -- Lvalue_read: p EC_0 ARCH |- storageReference : value_base -- if value = $bitacc_op(value_base, value_hi, value_lo)
12.5.4. Write
Click to view the specification source
rulegroup Lvalue_write/sliceAccess: rule Lvalue_write/sliceAccess: p EC_0 ARCH |- (storageReference `[value_hi : value_lo]) -> value : EC_1 -- Lvalue_read: p EC_0 ARCH |- storageReference : value_base -- if value_base_update = $bitacc_replace_op(value_base, value_hi, value_lo, value) -- Lvalue_write: p EC_0 ARCH |- storageReference -> value_base_update : EC_1
12.6. Parenthesized l-values
12.6.1. Type checking
Click to view the specification source
rulegroup Lvalue_ok/parenthesized: rule Lvalue_ok/parenthesized: p TC |- `(lvalue_base) : typedLvalueIR -- Lvalue_ok: p TC |- lvalue_base : typedLvalueIR_base -- if _ # `(typeIR_base) = typedLvalueIR_base -- if typedLvalueIR = (`(typedLvalueIR_base)) # `(typeIR_base)
12.6.2. Creation
Click to view the specification source
rulegroup Lvalue_eval/parenthesized: rule Lvalue_eval/parenthesized: p EC_0 ARCH_0 |- (`(typedLvalueIR)) # _ : EC_1 ARCH_1 storageReferenceResult -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 storageReferenceResult
13. Statements
The syntax of statements is defined as follows:
statement
: emptyStatement
| assignmentStatement
| callStatement
| directApplicationStatement
| returnStatement
| exitStatement
| blockStatement
| conditionalStatement
| forStatement
| breakStatement
| continueStatement
| switchStatement
;
13.1. Semantics of statements
13.1.1. Type checking
Click to view the specification source
relation Stmt_ok: cursor typingContext flow loopctxt |- statement : typingContext flow statementIR
After type checking, statements are represented in P4IR as follows:
statementIR
: emptyStatementIR
| assignmentStatementIR
| callStatementIR
| directApplicationStatementIR
| returnStatementIR
| exitStatementIR
| blockStatementIR
| conditionalStatementIR
| forStatementIR
| breakStatementIR
| continueStatementIR
| switchStatementIR
;
In order to statically determine the control flow of a P4 program (e.g., whether a function body always returns), the following indicator for abstract control flow is used:
flow
: CONT
| RET
;
As a marker of whether a statement is nested inside a loop, the following indicator is used:
loopctxt
: LOOP
| NOLOOP
;
13.1.2. Compile-time evaluation
Click to view the specification source
relation Stmt_inst: cursor instContext store |- statementIR : instContext store statementIR
Instantiations nested within statements are processed according to the above relation.
13.1.3. Runtime evaluation
Click to view the specification source
relation Stmt_eval: cursor evalContext arch |- statementIR : evalContext arch statementResult
The result of evaluating a statement is represented as follows:
statementResult
: continueEmptyResult
| callResult
| forResult
;
continueEmptyResult
: /* empty */
;
The following is the result of evaluating a call statement:
callResult
: abortResult
| returnResult
;
abortResult
: exitResult
| rejectTransitionResult
;
returnResult
: RETURN value?
;
exitResult
: EXIT
;
rejectTransitionResult
: REJECT errorValue
;
The following is the result of evaluating a for statement:
forResult
: forBreakResult
| forContinueResult
;
forBreakResult
: BREAK
;
forContinueResult
: CONTINUE
;
The subsequent sections describe each kind of statement in detail.
13.2. Empty statements
The empty statement, written ; is a no-op.
emptyStatement
: ;
;
13.2.1. Type checking
After type checking, an empty statement is represented as:
emptyStatementIR = emptyStatement
Click to view the specification source
rulegroup Stmt_ok/emptyStatement: rule Stmt_ok/emptyStatement: p TC f l |- ; : TC f ;
13.2.2. Compile-time evaluation
Click to view the specification source
rulegroup Stmt_inst/emptyStatementIR: rule Stmt_inst/emptyStatementIR: p IC STO |- emptyStatementIR : IC STO emptyStatementIR
13.2.3. Runtime evaluation
Click to view the specification source
rulegroup Stmt_eval/emptyStatementIR: rule Stmt_eval/emptyStatementIR: p EC ARCH |- ; : EC ARCH `EMPTY
13.3. Assignment statements
assignop
: =
| +=
| -=
| |+|=
| |-|=
| *=
| /=
| %=
| <<=
| >>=
| &=
| ^=
| |=
;
assignmentStatement
: lvalue assignop expression ;
;
An assignment, written with the = sign, first evaluates its left
sub-expression to an l-value, then evaluates its right sub-expression to a
value, and finally copies the value into the l-value. Derived types (e.g.
structs) are copied recursively, and all components of headers are
copied, including validity bits. Assignment is not defined for extern
values.
An assignment may also be written with a binary arithmetic or bit manipulation
operator immediately before the = sign. This performs the binary operator on
the old value of the left sub-expression and the right sub-expression and
assigns the result to the l-value. Thus an assignment like A += B is
equivalent to A = A + B, except that A is only evaluated once. This means
that any side-effects within this operand (eg, a function call inside an array
index or slice expression) only occur once. This is not valid for comparison
operators, logical operators, concat, range, or mask operators.
13.3.1. Type checking
After type checking, an assignment statement has the form:
assignmentStatementIR
: typedLvalueIR assignop typedExpressionIR ;
;
Click to view the specification source
rulegroup Stmt_ok/assignmentStatement: rule Stmt_ok/eq: p TC f l |- lvalue = expression ; : TC f (typedLvalueIR = typedExpressionIR_cast ;) -- Lvalue_ok: p TC |- lvalue : typedLvalueIR -- Expr_ok: p TC |- expression : typedExpressionIR -- if typeIR_l = $type_of_typedLvalueIR(typedLvalueIR) -- if typeIR_r = $type_of_typedExpressionIR(typedExpressionIR) -- if typedExpressionIR_cast = $cast_unary(typedExpressionIR, typeIR_l) rule Stmt_ok/compound: p TC f l |- lvalue assignop expression ; : TC f (typedLvalueIR assignop typedExpressionIR_cast ;) -- if assignop =/= = -- if binop = $assignop_as_binop(assignop) -- Lvalue_ok: p TC |- lvalue : typedLvalueIR -- if expression_lvalue = $lvalue_as_expression(lvalue) -- if expression_binary = expression_lvalue binop expression -- Expr_ok: p TC |- expression_binary : typedExpressionIR_binary -- if _ # `(typeIR_lvalue) = typedLvalueIR -- if (_ binop typedExpressionIR_cast) # _ = $cast_unary(typedExpressionIR_binary, typeIR_lvalue)
13.3.2. Compile-time evaluation
Click to view the specification source
rulegroup Stmt_inst/assignmentStatementIR: rule Stmt_inst/assignmentStatementIR: p IC STO |- assignmentStatementIR : IC STO assignmentStatementIR
13.3.3. Runtime evaluation
Click to view the specification source
rulegroup Stmt_eval/assignmentStatementIR: rule Stmt_eval/typedLvalueIR-abort: p EC_0 ARCH_0 |- typedLvalueIR assignop typedExpressionIR ; : EC_1 ARCH_1 abortResult -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 abortResult rule Stmt_eval/typedLvalueIR-cont-eq-typedExpressionIR-abort: p EC_0 ARCH_0 |- typedLvalueIR assignop typedExpressionIR ; : EC_2 ARCH_2 abortResult -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 (` storageReference) -- if assignop = = -- Expr_eval: p EC_1 ARCH_1 |- typedExpressionIR : EC_2 ARCH_2 abortResult rule Stmt_eval/typedLvalueIR-cont-compound-typedExpressionIR-abort: p EC_0 ARCH_0 |- typedLvalueIR assignop typedExpressionIR ; : EC_2 ARCH_2 abortResult -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 (` storageReference) -- if assignop =/= = -- Lvalue_read: p EC_1 ARCH_1 |- storageReference : value_l -- Expr_eval: p EC_1 ARCH_1 |- typedExpressionIR : EC_2 ARCH_2 abortResult rule Stmt_eval/typedLvalueIR-cont-eq-typedExpressionIR-cont: p EC_0 ARCH_0 |- typedLvalueIR assignop typedExpressionIR ; : EC_3 ARCH_2 `EMPTY -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 (` storageReference) -- if assignop = = -- Expr_eval: p EC_1 ARCH_1 |- typedExpressionIR : EC_2 ARCH_2 (` value) -- Lvalue_write: p EC_2 ARCH_2 |- storageReference -> value : EC_3 rule Stmt_eval/typedLvalueIR-cont-compound-typedExpressionIR-cont: p EC_0 ARCH_0 |- typedLvalueIR assignop typedExpressionIR ; : EC_3 ARCH_2 `EMPTY -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 (` storageReference) -- if assignop =/= = -- Lvalue_read: p EC_1 ARCH_1 |- storageReference : value_l -- Expr_eval: p EC_1 ARCH_1 |- typedExpressionIR : EC_2 ARCH_2 (` value_r) -- if binop = $assignop_as_binop(assignop) -- if value_update = $bin_op(binop, value_l, value_r) -- Lvalue_write: p EC_2 ARCH_2 |- storageReference -> value_update : EC_3
13.4. Call statements
Call statements are used to invoke actions, functions, and methods.
callStatement
: lvalue `( argumentList ) ;
| lvalue `< typeArgumentList > `( argumentList ) ;
;
Details of how calls are resolved and evaluated are described in Chapter 18.
13.4.1. Type checking
After type checking, a call statement is represented as:
callStatementIR
: callableTargetIR `< typeArgumentListIR > `( argumentListIR ) ;
;
callableTargetIR
: referenceExpressionIR
| typedExpressionIR . nameIR
| TYPE prefixedNameIR . nameIR
| `( callableTargetIR )
;
Click to view the specification source
rulegroup Stmt_ok/callStatement: rule Stmt_ok/callStatement-no-typeArgumentList-static_assert: p TC f l |- lvalue_callable `(argumentList) ; : TC f emptyStatementIR -- CallableTarget_lvalue_ok: p TC |- lvalue_callable : callableTargetIR -- ArgumentList_ok: p TC |- argumentList : argumentIR* -- CallableType_ok: p TC |- callableTargetIR `<eps> `(argumentIR*) : callableTypeIR `<# typeId_impl*> `(# id_default* # id_optional*) -- Call_ok: p TC |- callableTypeIR `<eps # typeId_impl*> `(argumentIR* # id_default* # id_optional*) : typeIR_ret `<typeArgumentIR_inferred*> `(argumentIR_cast*) -- if $is_static_assert_callableTypeIR(callableTypeIR) -- if callExpressionIR = callableTargetIR `<typeArgumentIR_inferred*> `(argumentIR_cast*) -- if expressionNoteIR = `(typeIR_ret LCTK) -- Expr_eval_lctk: p TC |- (callExpressionIR # expressionNoteIR) ~> (`B true) -- if emptyStatementIR = ; rule Stmt_ok/callStatement-no-typeArgumentList: p TC f l |- lvalue_callable `(argumentList) ; : TC f callStatementIR -- CallableTarget_lvalue_ok: p TC |- lvalue_callable : callableTargetIR -- ArgumentList_ok: p TC |- argumentList : argumentIR* -- CallableType_ok: p TC |- callableTargetIR `<eps> `(argumentIR*) : callableTypeIR `<# typeId_impl*> `(# id_default* # id_optional*) -- Call_ok: p TC |- callableTypeIR `<eps # typeId_impl*> `(argumentIR* # id_default* # id_optional*) : typeIR_ret `<typeArgumentIR_inferred*> `(argumentIR_cast*) -- if ~$is_static_assert_callableTypeIR(callableTypeIR) -- if callStatementIR = callableTargetIR `<typeArgumentIR_inferred*> `(argumentIR_cast*) ; rule Stmt_ok/callStatement-typeArgumentList: p TC f l |- lvalue_callable `<typeArgumentList> `(argumentList) ; : TC f callStatementIR -- CallableTarget_lvalue_ok: p TC |- lvalue_callable : callableTargetIR -- TypeArgumentList_ok: p TC |- typeArgumentList : typeArgumentIR* # typeId_impl* -- ArgumentList_ok: p TC |- argumentList : argumentIR* -- CallableType_ok: p TC |- callableTargetIR `<typeArgumentIR*> `(argumentIR*) : callableTypeIR `<# typeId_inserted*> `(# id_default* # id_optional*) -- if typeId_infer* = typeId_impl* ++ typeId_inserted* -- Call_ok: p TC |- callableTypeIR `<typeArgumentIR* # typeId_infer*> `(argumentIR* # id_default* # id_optional*) : typeIR_ret `<typeArgumentIR_inferred*> `(argumentIR_cast*) -- if callStatementIR = callableTargetIR `<typeArgumentIR_inferred*> `(argumentIR_cast*) ;
13.4.2. Compile-time evaluation
Click to view the specification source
rulegroup Stmt_inst/callStatementIR: rule Stmt_inst/callStatementIR: p IC STO |- callStatementIR : IC STO callStatementIR
13.4.3. Runtime evaluation
Click to view the specification source
rulegroup Stmt_eval/callStatementIR: rule Stmt_eval/callee-abort: p EC_0 ARCH_0 |- callableTargetIR `<typeArgumentIR*> `(argumentIR*) ; : EC_1 ARCH_1 abortResult -- Callee_eval: p EC_0 ARCH_0 |- callableTargetIR `<_> `(argumentIR*) : EC_1 ARCH_1 abortResult rule Stmt_eval/callee-cont-call-abort: p EC_0 ARCH_0 |- callableTargetIR `<typeArgumentIR*> `(argumentIR*) ; : EC_2 ARCH_2 abortResult -- Callee_eval: p EC_0 ARCH_0 |- callableTargetIR `<_> `(argumentIR*) : EC_1 ARCH_1 (` callee) -- Call_eval: p EC_1 ARCH_1 |- callee @ `<typeArgumentIR*> `(argumentIR*) : EC_2 ARCH_2 abortResult rule Stmt_eval/callee-cont-call-return: p EC_0 ARCH_0 |- callableTargetIR `<typeArgumentIR*> `(argumentIR*) ; : EC_2 ARCH_2 `EMPTY -- Callee_eval: p EC_0 ARCH_0 |- callableTargetIR `<_> `(argumentIR*) : EC_1 ARCH_1 (` callee) -- Call_eval: p EC_1 ARCH_1 |- callee @ `<typeArgumentIR*> `(argumentIR*) : EC_2 ARCH_2 returnResult
13.5. Direct type invocation
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
}
}
directApplicationStatement
: namedType . APPLY `( argumentList ) ;
;
This feature is intended to streamline the common case where a type is instantiated exactly once. 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 [sec-name-annotations] 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.
13.5.1. Type checking
After type checking, a direct application statement is represented as:
directApplicationStatementIR
: constructorTargetIR . APPLY `( argumentListIR ) ;
;
Click to view the specification source
rulegroup Stmt_ok/directApplicationStatement: rule Stmt_ok/directApplicationStatement: p TC_0 f l |- namedType . APPLY `(argumentList) ; : TC_0 f directApplicationStatementIR -- Expr_ok: p TC_0 |- namedType `(`EMPTY) : (constructorTargetIR `(eps)) # `(typeIR_object _) -- if prefixedNameIR `<typeArgumentIR*> = constructorTargetIR -- if typeIR_object_unroll = $unroll_typeIR(typeIR_object) -- if (typeIR_object_unroll <: parserObjectTypeIR) \/ (typeIR_object_unroll <: controlObjectTypeIR) -- if nameIR_object = "__direct_application" -- if TC_1 = $add_var_t(p, TC_0, nameIR_object, `EMPTY typeIR_object CTK eps) -- if lvalue = (`ID nameIR_object) . (`ID "apply") -- Stmt_ok: p TC_1 f l |- lvalue `(argumentList) ; : _ _ callStatementIR -- if (typedExpressionIR_base . "apply") `<eps> `(argumentIR_cast*) ; = callStatementIR -- if (` nameIR_object) # `(typeIR_object CTK) = typedExpressionIR_base -- if constructorTargetIR = prefixedNameIR `<typeArgumentIR*> -- if directApplicationStatementIR = constructorTargetIR . APPLY `(argumentIR_cast*) ;
13.5.2. Compile-time evaluation
At compile-time, a direct application is instantiated, and then desugared into a method call on that instance with the relation:
Click to view the specification source
relation DirectApplicationStmt_inst: cursor instContext store |- directApplicationStatementIR : store constantDeclarationIR callStatementIR
It is implemented as:
Click to view the specification source
rulegroup DirectApplicationStmt_inst: rule DirectApplicationStmt_inst: p IC STO_0 |- constructorTargetIR . APPLY `(argumentListIR) ; : STO_2 constantDeclarationIR callStatementIR -- if prefixedNameIR `<typeArgumentListIR> = constructorTargetIR -- Constructor_inst: p IC |- prefixedNameIR `<typeArgumentListIR> `(eps) : constructorDef `<typeArgumentListIR_inst> `(# id_default* # id_optional*) -- if typeId = $flatten_prefixedNameIR(prefixedNameIR) -- if IC_callee = $enter_path_i(IC, typeId) -- Constructor_call: p IC_callee STO_0 |- constructorDef `<typeArgumentListIR_inst> `(eps # id_default* # id_optional*) : STO_1 object -- if typeId_fresh = $fresh_typeId -- if typeIR = ` typeId -- if nameIR = $concat_text([typeId, "_", typeId_fresh]) -- if objectId = IC.PATH ++ typeId -- if STO_2 = $add_store(STO_1, objectId, object) -- if constantDeclarationIR = `EMPTY CONST typeIR nameIR (= `VALUE (REF objectId)) ; -- if typedExpressionIR_base = (` nameIR) # `(typeIR CTK) -- if callableTargetIR = typedExpressionIR_base . "apply" -- if callStatementIR = callableTargetIR `<typeArgumentListIR_inst> `(argumentListIR) ;
A direct application statement is instantiated as:
Click to view the specification source
rulegroup Stmt_inst/directApplicationStatementIR:
rule Stmt_inst/directApplicationStatementIR:
p IC STO_0 |- directApplicationStatementIR : IC STO_1 blockStatementIR
-- DirectApplicationStmt_inst: p IC STO_0 |- directApplicationStatementIR : STO_1 constantDeclarationIR callStatementIR
-- if blockStatementIR = `EMPTY `{[constantDeclarationIR, callStatementIR]}
13.6. Return statements
The return statement immediately terminates the execution of the action,
function or control containing it. return statements are not allowed
within parsers. return statements followed by an expression are only allowed
within functions that return values; in this case the type of the expression
must match the return type of the function. Any copy-out behavior due to
direction out or inout parameters of the enclosing action, function, or
control are still performed after the execution of the return statement.
See Section 18.4 for details on copy-out behavior.
returnStatement
: RETURN ;
| RETURN expression ;
;
13.6.1. Type checking
After type checking, a return statement has the form:
returnStatementIR
: RETURN ;
| RETURN typedExpressionIR ;
;
Click to view the specification source
rulegroup Stmt_ok/returnStatement: rule Stmt_ok/non-expression: LOCAL TC f l |- RETURN ; : TC RET (RETURN ;) -- if VOID = $find_local_return_type_t(TC) rule Stmt_ok/expression: LOCAL TC f l |- RETURN expression ; : TC RET (RETURN typedExpressionIR_cast ;) -- Expr_ok: LOCAL TC |- expression : typedExpressionIR -- if typeIR = $type_of_typedExpressionIR(typedExpressionIR) -- if typeIR_ret = $find_local_return_type_t(TC) -- if typedExpressionIR_cast = $cast_unary(typedExpressionIR, typeIR_ret)
13.6.2. Compile-time evaluation
Click to view the specification source
rulegroup Stmt_inst/returnStatementIR: rule Stmt_inst/returnStatementIR: LOCAL IC STO |- returnStatementIR : IC STO returnStatementIR
13.6.3. Runtime evaluation
Click to view the specification source
rulegroup Stmt_eval/returnStatementIR: rule Stmt_eval/non-expression: LOCAL EC ARCH |- RETURN ; : EC ARCH (RETURN eps) rule Stmt_eval/expression-abort: LOCAL EC_0 ARCH_0 |- RETURN typedExpressionIR ; : EC_1 ARCH_1 abortResult -- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 abortResult rule Stmt_eval/expression-cont: LOCAL EC_0 ARCH_0 |- RETURN typedExpressionIR ; : EC_1 ARCH_1 (RETURN value) -- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 (` value)
13.7. Exit statements
The exit statement immediately terminates the execution of all the blocks
currently executing: the current action (if invoked within an action), the
current control, and all its callers. exit statements are not allowed
within parsers or functions.
Any copy-out behavior due to direction out or inout parameters of the
enclosing action or control, and all of its callers, are still performed
after the execution of the exit statement. See Section 18.4 for
details on copy-out behavior.
exitStatement
: EXIT ;
;
There are some expressions whose evaluation might cause an exit statement to
be executed. Examples include:
-
table.apply().action_run, which can only appear as the expression of aswitchstatement (see Section 13.13), and when it appears there, it must be the entire expression. -
Any expression containing
table.apply().hitortable.apply().miss(see Section 16.3.7), which can be part of arbitrarily complex expressions in many places of a P4 program, such as:-
the expression in an
ifstatement. -
the expression
e1in a conditional expressione1 ? e2 : e3. -
in an assignment statement, in the left and/or right hand sides.
-
an argument passed to a function or method call.
-
an expression to calculate a table key (see [sec-mau-semantics]).
-
This list is not intended to be exhaustive.
If applying the table causes an action to be executed, which in turn causes an
exit statement to be executed, then evaluation of the expression ends
immediately, and the rest of the current expression or statement does not
complete its execution. See Section 14.1 for the order of
evaluation of the parts of an expression. For the examples listed above, it
also means the following behavior after the expression evaluation is
interrupted.
-
For a
switchstatement, iftable.apply()exits, then none of the blocks in theswitchstatement are executed. -
If
table.apply().hitortable.apply().misscause an exit during the evaluation of an expression:-
If it is the expression of an
ifstatement, then neither the 'then' nor 'else' branches of theifstatement are executed. -
If it is the expression
e1in a conditional expressione1 ? e2 : e3, then neither expressione2nore3are evaluated. -
If the expression is the right hand side of an assignment statement, or part of the calculation of the L-value on the left hand side (e.g. the index expression of a header stack reference), then no assignment occurs.
-
If the expression is an argument passed to a function or method call, then the function/method call does not occur.
-
If the expression is a table key, then the table is not applied.
-
13.7.1. Type checking
After type checking, an exit statement is represented in P4IR as:
exitStatementIR = exitStatement
Click to view the specification source
rulegroup Stmt_ok/exitStatement: rule Stmt_ok/exitStatement: p TC f l |- EXIT ; : TC f (EXIT ;)
13.7.2. Compile-time evaluation
Click to view the specification source
rulegroup Stmt_inst/exitStatementIR: rule Stmt_inst/exitStatementIR: p IC STO |- exitStatementIR : IC STO exitStatementIR
13.7.3. Runtime evaluation
Click to view the specification source
rulegroup Stmt_eval/exitStatementIR: rule Stmt_eval/exitStatementIR: p EC ARCH |- EXIT ; : EC ARCH EXIT
13.8. Block statements
A block statement is denoted by curly braces. It contains a sequence of statements and declarations, which are executed sequentially. The declarations (e.g., variables and constants) within a block statement are only visible within the block.
blockStatement
: annotationList `{ blockElementStatementList }
;
blockElementStatementList
: /* empty */
| blockElementStatementList blockElementStatement
;
blockElementStatement
: constantDeclaration
| variableDeclaration
| statement
;
13.8.1. Type checking
After type checking, block statements are represented in P4IR as:
blockStatementIR
: annotationList `{ blockElementStatementListIR }
;
blockElementStatementListIR = blockElementStatementIR*
blockElementStatementIR
: constantDeclarationIR
| variableDeclarationIR
| statementIR
;
Click to view the specification source
rulegroup Stmt_ok/blockStatement: rule Stmt_ok/blockStatement: LOCAL TC_0 f_0 l |- blockStatement : TC_3 f_1 blockStatementIR -- if TC_1 = $enter_t(TC_0) -- Block_ok: TC_1 f_0 l |- blockStatement : TC_2 f_1 blockStatementIR -- if TC_3 = $exit_t(TC_2)
A block is type checked by the relation:
Click to view the specification source
relation Block_ok: typingContext flow loopctxt |- blockStatement : typingContext flow blockStatementIR
Click to view the specification source
rulegroup Block_ok:
rule Block_ok:
TC_0 f l |- annotationList `{blockElementStatementList} : TC_1 f_post blockStatementIR
-- BlockElementStmtList_ok: TC_0 f l |- blockElementStatementList : TC_1 f_post blockElementStatementIR*
-- if blockStatementIR = annotationList `{blockElementStatementIR*}
A statement or declaration within a block statement is type checked by the relation:
Click to view the specification source
relation BlockElementStmt_ok: typingContext flow loopctxt |- blockElementStatement : typingContext flow blockElementStatementIR
Click to view the specification source
rulegroup BlockElementStmt_ok: rule BlockElementStmt_ok/constantDeclaration: TC_0 f l |- constantDeclaration : TC_1 f constantDeclarationIR -- ConstDecl_ok: LOCAL TC_0 |- constantDeclaration : TC_1 constantDeclarationIR rule BlockElementStmt_ok/variableDeclaration: TC_0 f l |- variableDeclaration : TC_1 f variableDeclarationIR -- VarDecl_ok: LOCAL TC_0 |- variableDeclaration : TC_1 variableDeclarationIR rule BlockElementStmt_ok/statement: TC_0 f l |- statement : TC_1 f_post statementIR -- Stmt_ok: LOCAL TC_0 f l |- statement : TC_1 f_post statementIR
A list of block elements is type checked by the relation:
Click to view the specification source
relation BlockElementStmtList_ok: typingContext flow loopctxt |- blockElementStatementList : typingContext flow blockElementStatementIR*
Click to view the specification source
rulegroup BlockElementStmtList_ok: rule BlockElementStmtList_ok: TC_0 f l |- blockElementStatementList : TC_1 f_post blockElementStatementIR* -- if blockElementStatement* = $flatten_blockElementStatementList(blockElementStatementList) -- BlockElementStmts_ok: TC_0 f l |- blockElementStatement* : TC_1 f_post blockElementStatementIR*
Click to view the specification source
relation BlockElementStmts_ok: typingContext flow loopctxt |- blockElementStatement* : typingContext flow blockElementStatementIR*
Click to view the specification source
rulegroup BlockElementStmts_ok: rule BlockElementStmts_ok/nil: TC f l |- eps : TC f eps rule BlockElementStmts_ok/cons: TC_0 f_0 l |- blockElementStatement_h :: blockElementStatement_t* : TC_2 f_2 (blockElementStatementIR_h :: blockElementStatementIR_t*) -- BlockElementStmt_ok: TC_0 f_0 l |- blockElementStatement_h : TC_1 f_1 blockElementStatementIR_h -- BlockElementStmts_ok: TC_1 f_1 l |- blockElementStatement_t* : TC_2 f_2 blockElementStatementIR_t*
13.8.2. Compile-time evaluation
Click to view the specification source
rulegroup Stmt_inst/blockStatementIR:
rule Stmt_inst/blockStatementIR:
LOCAL IC_0 STO_0 |- annotationList `{blockElementStatementListIR} : IC_3 STO_1 (annotationList `{blockElementStatementListIR_inst})
-- if IC_1 = $enter_i(IC_0)
-- Block_inst: IC_1 STO_0 |- annotationList `{blockElementStatementListIR} : IC_2 STO_1 (_ `{blockElementStatementListIR_inst})
-- if IC_3 = $exit_i(IC_2)
A block is compile-time evaluated by the relation:
Click to view the specification source
relation Block_inst: instContext store |- blockStatementIR : instContext store blockStatementIR
Click to view the specification source
rulegroup Block_inst:
rule Block_inst:
IC_0 STO_0 |- annotationList `{blockElementStatementListIR} : IC_1 STO_1 (annotationList `{blockElementStatementListIR_inst})
-- BlockElementStmtList_inst: IC_0 STO_0 |- blockElementStatementListIR : IC_1 STO_1 blockElementStatementListIR_inst
A block element is compile-time evaluated by the relation:
Click to view the specification source
relation BlockElementStmt_inst: instContext store |- blockElementStatementIR : instContext store blockElementStatementIR
Click to view the specification source
rulegroup BlockElementStmt_inst: rule BlockElementStmt_inst/constantDeclarationIR: IC_0 STO_0 |- constantDeclarationIR : IC_1 STO_0 constantDeclarationIR -- ConstDecl_inst: LOCAL IC_0 |- constantDeclarationIR : IC_1 rule BlockElementStmt_inst/variableDeclarationIR: IC STO |- variableDeclarationIR : IC STO variableDeclarationIR rule BlockElementStmt_inst/statementIR: IC_0 STO_0 |- statementIR : IC_1 STO_1 statementIR_inst -- Stmt_inst: LOCAL IC_0 STO_0 |- statementIR : IC_1 STO_1 statementIR_inst
A list of block elements is compile-time evaluated by the relation:
Click to view the specification source
relation BlockElementStmtList_inst: instContext store |- blockElementStatementListIR : instContext store blockElementStatementIR*
Click to view the specification source
rulegroup BlockElementStmtList_inst: rule BlockElementStmtList_inst/nil: IC STO |- eps : IC STO eps rule BlockElementStmtList_inst/cons: IC_0 STO_0 |- blockElementStatementIR_h :: blockElementStatementIR_t* : IC_2 STO_2 (blockElementStatementIR_h_inst :: blockElementStatementIR_t_inst*) -- BlockElementStmt_inst: IC_0 STO_0 |- blockElementStatementIR_h : IC_1 STO_1 blockElementStatementIR_h_inst -- BlockElementStmtList_inst: IC_1 STO_1 |- blockElementStatementIR_t* : IC_2 STO_2 blockElementStatementIR_t_inst*
13.8.3. Runtime evaluation
Click to view the specification source
rulegroup Stmt_eval/blockStatementIR: rule Stmt_eval/blockStatementIR: p EC_0 ARCH_0 |- blockStatementIR : EC_3 ARCH_1 statementResult -- if EC_1 = $enter_e(EC_0) -- Block_eval: EC_1 ARCH_0 |- blockStatementIR : EC_2 ARCH_1 statementResult -- if EC_3 = $exit_e(EC_2)
A block is runtime evaluated by the relation:
Click to view the specification source
relation Block_eval: evalContext arch |- blockStatementIR : evalContext arch statementResult
Click to view the specification source
rulegroup Block_eval:
rule Block_eval:
EC_0 ARCH_0 |- annotationList `{blockElementStatementListIR} : EC_1 ARCH_1 statementResult
-- BlockElementStmtList_eval: EC_0 ARCH_0 |- blockElementStatementListIR : EC_1 ARCH_1 statementResult
A block element is runtime evaluated by the relation:
Click to view the specification source
relation BlockElementStmt_eval: evalContext arch |- blockElementStatementIR : evalContext arch statementResult
Click to view the specification source
rulegroup BlockElementStmt_eval: rule BlockElementStmt_eval/constantDeclarationIR: EC_0 ARCH |- constantDeclarationIR : EC_1 ARCH `EMPTY -- ConstDecl_eval: LOCAL EC_0 |- constantDeclarationIR : EC_1 rule BlockElementStmt_eval/variableDeclarationIR: EC_0 ARCH_0 |- variableDeclarationIR : EC_1 ARCH_1 declarationResult -- VarDecl_eval: LOCAL EC_0 ARCH_0 |- variableDeclarationIR : EC_1 ARCH_1 declarationResult rule BlockElementStmt_eval/statementIR: EC_0 ARCH_0 |- statementIR : EC_1 ARCH_1 statementResult -- Stmt_eval: LOCAL EC_0 ARCH_0 |- statementIR : EC_1 ARCH_1 statementResult
A list of block elements is runtime evaluated by the relation:
Click to view the specification source
relation BlockElementStmtList_eval: evalContext arch |- blockElementStatementListIR : evalContext arch statementResult
Click to view the specification source
rulegroup BlockElementStmtList_eval: rule BlockElementStmtList_eval/nil: EC ARCH |- eps : EC ARCH `EMPTY rule BlockElementStmtList_eval/cons-head-non-cont: EC_0 ARCH_0 |- blockElementStatementIR_h :: blockElementStatementIR_t* : EC_1 ARCH_1 statementResult -- BlockElementStmt_eval: EC_0 ARCH_0 |- blockElementStatementIR_h : EC_1 ARCH_1 statementResult -- if statementResult =/= `EMPTY rule BlockElementStmtList_eval/cons-head-cont: EC_0 ARCH_0 |- blockElementStatementIR_h :: blockElementStatementIR_t* : EC_2 ARCH_2 statementResult -- BlockElementStmt_eval: EC_0 ARCH_0 |- blockElementStatementIR_h : EC_1 ARCH_1 `EMPTY -- BlockElementStmtList_eval: EC_1 ARCH_1 |- blockElementStatementIR_t* : EC_2 ARCH_2 statementResult
13.9. Conditional statements
The conditional statement uses standard syntax and semantics familiar from many programming languages.
However, the condition expression in P4 is required to be a Boolean (and not an integer).
conditionalStatement
: IF `( expression ) statement
| IF `( expression ) statement ELSE statement
;
When several if statements are nested, the else applies to the innermost
if statement that does not have an else statement.
13.9.1. Type checking
After type checking, a conditional statement has the form:
conditionalStatementIR
: IF `( typedExpressionIR ) statementIR
| IF `( typedExpressionIR ) statementIR ELSE statementIR
;
Click to view the specification source
rulegroup Stmt_ok/conditionalStatement: rule Stmt_ok/non-else: p TC f l |- IF `(expression_cond) statement_then : TC f (IF `(typedExpressionIR_cond) statementIR_then) -- Expr_ok: p TC |- expression_cond : typedExpressionIR_cond -- if typeIR_cond = $type_of_typedExpressionIR(typedExpressionIR_cond) -- if BOOL = $unroll_typeIR(typeIR_cond) -- Stmt_ok: p TC f l |- statement_then : TC_then f_then statementIR_then rule Stmt_ok/else: p TC f l |- IF `(expression_cond) statement_then ELSE statement_else : TC f_post (IF `(typedExpressionIR_cond) statementIR_then ELSE statementIR_else) -- Expr_ok: p TC |- expression_cond : typedExpressionIR_cond -- if typeIR_cond = $type_of_typedExpressionIR(typedExpressionIR_cond) -- if BOOL = $unroll_typeIR(typeIR_cond) -- Stmt_ok: p TC f l |- statement_then : TC_then f_then statementIR_then -- Stmt_ok: p TC f l |- statement_else : TC_else f_else statementIR_else -- if f_post = $join_flow(f_then, f_else)
13.9.2. Compile-time evaluation
Click to view the specification source
rulegroup Stmt_inst/conditionalStatementIR: rule Stmt_inst/non-else: p IC STO_0 |- IF `(typedExpressionIR_cond) statementIR_then : IC STO_1 (IF `(typedExpressionIR_cond) statementIR_then_inst) -- Stmt_inst: p IC STO_0 |- statementIR_then : IC_then STO_1 statementIR_then_inst rule Stmt_inst/else: p IC STO_0 |- IF `(typedExpressionIR_cond) statementIR_then ELSE statementIR_else : IC STO_2 (IF `(typedExpressionIR_cond) statementIR_then_inst ELSE statementIR_else_inst) -- Stmt_inst: p IC STO_0 |- statementIR_then : IC_then STO_1 statementIR_then_inst -- Stmt_inst: p IC STO_1 |- statementIR_else : IC_else STO_2 statementIR_else_inst
13.9.3. Runtime evaluation
Click to view the specification source
rulegroup Stmt_eval/conditionalStatementIR: rule Stmt_eval/non-else-abort: p EC ARCH |- IF `(typedExpressionIR_cond) statementIR_then : EC_cond ARCH_cond abortResult -- Expr_eval: p EC ARCH |- typedExpressionIR_cond : EC_cond ARCH_cond abortResult rule Stmt_eval/non-else-cont-true: p EC ARCH |- IF `(typedExpressionIR_cond) statementIR_then : EC_then ARCH_then statementResult -- Expr_eval: p EC ARCH |- typedExpressionIR_cond : EC_cond ARCH_cond (` (`B true)) -- Stmt_eval: p EC_cond ARCH_cond |- statementIR_then : EC_then ARCH_then statementResult rule Stmt_eval/non-else-cont-false: p EC ARCH |- IF `(typedExpressionIR_cond) statementIR_then : EC_cond ARCH_cond `EMPTY -- Expr_eval: p EC ARCH |- typedExpressionIR_cond : EC_cond ARCH_cond (` (`B false)) rule Stmt_eval/else-abort: p EC ARCH |- IF `(typedExpressionIR_cond) statementIR_then ELSE statementIR_else : EC_cond ARCH_cond abortResult -- Expr_eval: p EC ARCH |- typedExpressionIR_cond : EC_cond ARCH_cond abortResult rule Stmt_eval/else-cont-true: p EC ARCH |- IF `(typedExpressionIR_cond) statementIR_then ELSE statementIR_else : EC_then ARCH_then statementResult -- Expr_eval: p EC ARCH |- typedExpressionIR_cond : EC_cond ARCH_cond (` (`B true)) -- Stmt_eval: p EC_cond ARCH_cond |- statementIR_then : EC_then ARCH_then statementResult rule Stmt_eval/else-cont-false: p EC ARCH |- IF `(typedExpressionIR_cond) statementIR_then ELSE statementIR_else : EC_else ARCH_else statementResult -- Expr_eval: p EC ARCH |- typedExpressionIR_cond : EC_cond ARCH_cond (` (`B false)) -- Stmt_eval: p EC_cond ARCH_cond |- statementIR_else : EC_else ARCH_else statementResult
13.10. For statements
A for statement executes a statement zero or more times, based on a
condition or a range or collection.
forStatement
: annotationList FOR
`( forInitStatementList ; expression ; forUpdateStatementList ) statement
| annotationList FOR `( type name IN forCollectionExpression ) statement
| annotationList FOR
`( annotationListNonEmpty type name IN forCollectionExpression )
statement
;
forInitStatementList
: /* empty */
| forInitStatementListNonEmpty
;
forInitStatementListNonEmpty
: forInitStatement
| forInitStatementListNonEmpty , forInitStatement
;
forInitStatement
: annotationList type name initializerOpt
| forUpdateStatement
;
forUpdateStatementList
: /* empty */
| forUpdateStatementListNonEmpty
;
forUpdateStatementListNonEmpty
: forUpdateStatement
| forUpdateStatementListNonEmpty , forUpdateStatement
;
forUpdateStatement
: lvalue `( argumentList )
| lvalue `< typeArgumentList > `( argumentList )
| lvalue assignop expression
;
forCollectionExpression
: expression
| expression .. expression
;
There are two forms of for statements:
-
Basic 3-clause
forstatements with initialization, condition, and update. -
for-instatements that iterate over a range, list, or header stack.
A break; statement may be executed in a loop body to immediately exit the
loop, without executing the rest of the body or the update statements. break;
statements are explained in detail in Section 13.11.
A continue; statement may be executed in a loop body to skip the rest of the
current loop body, executing the update statements and then evaluating the
condition to either execute the loop body again, or terminate the loop. continue;
statements are explained in detail in Section 13.12.
The scope of any declaration in a for statement is limited to the for
statement and its body.
After type checking, a for statement has the form:
forStatementIR
: annotationList FOR
`( forInitStatementListIR ; typedExpressionIR ; forUpdateStatementListIR )
statementIR
| annotationList FOR `( typeIR nameIR IN forCollectionExpressionIR )
statementIR
| annotationList FOR
`( annotationList typeIR nameIR IN forCollectionExpressionIR )
statementIR
;
13.10.1. 3-clause for statements
The basic 3-clause for statement is similar to a C for statement. The init
statements will be executed prior to executing the loop. The condition will be
evaluated before each iteration, to determine if the loop should exit. If the
condition is false, the loop will exit without executing any statements from
the loop body or update. The update statements will be executed after the loop
body and before evaluating the condition again for the next iteration.
13.10.1.1. Type checking
After type checking, a 3-clause for statement has the form:
forStatementIR
: annotationList FOR
`( forInitStatementListIR ; typedExpressionIR ; forUpdateStatementListIR )
statementIR
| annotationList FOR `( typeIR nameIR IN forCollectionExpressionIR )
statementIR
| annotationList FOR
`( annotationList typeIR nameIR IN forCollectionExpressionIR )
statementIR
;
forInitStatementListIR = forInitStatementIR*
forInitStatementIR
: annotationList typeIR nameIR initializerOptIR
| forUpdateStatementIR
;
forUpdateStatementListIR = forUpdateStatementIR*
forUpdateStatementIR
: callableTargetIR `< typeArgumentListIR > `( argumentListIR )
| typedLvalueIR assignop typedExpressionIR
;
Click to view the specification source
rulegroup Stmt_ok/forStatement-three-clause: rule Stmt_ok/forStatement-three-clause: LOCAL TC_0 f l |- annotationList FOR `(forInitStatementList ; expression ; forUpdateStatementList) statement : TC_0 f_post forStatementIR -- if TC_1 = $enter_t(TC_0) -- ForInitStmtList_ok: TC_1 |- forInitStatementList : TC_2 forInitStatementIR* -- Expr_ok: LOCAL TC_2 |- expression : typedExpressionIR -- if typeIR = $type_of_typedExpressionIR(typedExpressionIR) -- if BOOL = $unroll_typeIR(typeIR) -- ForUpdateStmtList_ok: TC_2 |- forUpdateStatementList : forUpdateStatementIR* -- Stmt_ok: LOCAL TC_2 f LOOP |- statement : TC_3 f_post statementIR -- if TC_4 = $exit_t(TC_3) -- if forStatementIR = annotationList FOR `(forInitStatementIR* ; typedExpressionIR ; forUpdateStatementIR*) statementIR
A list of initialization statements are type checked with the relation:
Click to view the specification source
relation ForInitStmtList_ok: typingContext |- forInitStatementList : typingContext forInitStatementIR*
This relation invokes the following relation for each initialization statement:
Click to view the specification source
relation ForInitStmt_ok: typingContext |- forInitStatement : typingContext forInitStatementIR
Click to view the specification source
rulegroup ForInitStmt_ok: rule ForInitStmt_ok/variableDeclaration-non-initializer: TC_0 |- annotationList type name `EMPTY : TC_1 (annotationList typeIR nameIR eps) -- Type_ok: LOCAL TC_0 |- type : typeIR # eps -- Type_wf: $bound(LOCAL, TC_0) |- typeIR -- if $is_assignable_typeIR(typeIR) -- if nameIR = $name(name) -- if TC_1 = $add_var_t(LOCAL, TC_0, nameIR, INOUT typeIR DYN eps) rule ForInitStmt_ok/variableDeclaration-initializer: TC_0 |- annotationList type name (= expression_init) : TC_1 (annotationList typeIR nameIR (= typedExpressionIR_init_cast)) -- Type_ok: LOCAL TC_0 |- type : typeIR # eps -- Type_wf: $bound(LOCAL, TC_0) |- typeIR -- if $is_assignable_typeIR(typeIR) -- Expr_ok: LOCAL TC_0 |- expression_init : typedExpressionIR_init -- if typedExpressionIR_init_cast = $cast_unary(typedExpressionIR_init, typeIR) -- if nameIR = $name(name) -- if TC_1 = $add_var_t(LOCAL, TC_0, nameIR, INOUT typeIR DYN eps) rule ForInitStmt_ok/forUpdateStatement: TC |- forUpdateStatement : TC forUpdateStatementIR -- ForUpdateStmt_ok: TC |- forUpdateStatement : forUpdateStatementIR
A list of update statements are type checked with the relation:
Click to view the specification source
relation ForUpdateStmtList_ok: typingContext |- forUpdateStatementList : forUpdateStatementIR*
This relation invokes the following relation for each update statement:
Click to view the specification source
relation ForUpdateStmt_ok: typingContext |- forUpdateStatement : forUpdateStatementIR
Click to view the specification source
rulegroup ForUpdateStmt_ok: rule ForUpdateStmt_ok/call-no-typeArgumentList: TC |- lvalue_callable `(argumentList) : callableTargetIR `<typeArgumentIR*> `(argumentIR*) -- Stmt_ok: LOCAL TC CONT NOLOOP |- lvalue_callable `(argumentList) ; : _ _ (callableTargetIR `<typeArgumentIR*> `(argumentIR*) ;) rule ForUpdateStmt_ok/call-typeArgumentList: TC |- lvalue_callable `<typeArgumentList> `(argumentList) : callableTargetIR `<typeArgumentIR*> `(argumentIR*) -- Stmt_ok: LOCAL TC CONT NOLOOP |- lvalue_callable `<typeArgumentList> `(argumentList) ; : _ _ (callableTargetIR `<typeArgumentIR*> `(argumentIR*) ;) rule ForUpdateStmt_ok/assign: TC |- lvalue assignop expression : typedLvalueIR assignop typedExpressionIR -- Stmt_ok: LOCAL TC CONT NOLOOP |- lvalue assignop expression ; : _ _ (typedLvalueIR assignop typedExpressionIR ;)
13.10.1.2. Compile-time evaluation
Click to view the specification source
rulegroup Stmt_inst/forStatementIR-three-clause: rule Stmt_inst/forStatementIR-three-clause: p IC STO_0 |- annotationList FOR `(forInitStatementListIR ; typedExpressionIR ; forUpdateStatementListIR) statementIR : IC STO_1 forStatementIR -- Stmt_inst: p IC STO_0 |- statementIR : IC_body STO_1 statementIR_body -- if forStatementIR = annotationList FOR `(forInitStatementListIR ; typedExpressionIR ; forUpdateStatementListIR) statementIR_body
13.10.1.3. Runtime evaluation
Click to view the specification source
rulegroup Stmt_eval/forStatementIR-three-clause: rule Stmt_eval/init-abort: LOCAL EC_0 ARCH_0 |- annotationList FOR `(forInitStatementListIR ; typedExpressionIR ; forUpdateStatementListIR) statementIR : EC_3 ARCH_1 abortResult -- if EC_1 = $enter_e(EC_0) -- ForInitStmts_eval: EC_1 ARCH_0 |- forInitStatementListIR : EC_2 ARCH_1 abortResult -- if EC_3 = $exit_e(EC_2) rule Stmt_eval/init-cont: LOCAL EC_0 ARCH_0 |- annotationList FOR `(forInitStatementListIR ; typedExpressionIR ; forUpdateStatementListIR) statementIR : EC_4 ARCH_2 statementResult -- if EC_1 = $enter_e(EC_0) -- ForInitStmts_eval: EC_1 ARCH_0 |- forInitStatementListIR : EC_2 ARCH_1 `EMPTY -- ForThreeClauseStmt_eval: EC_2 ARCH_1 |- typedExpressionIR statementIR forUpdateStatementListIR : EC_3 ARCH_2 statementResult -- if EC_4 = $exit_e(EC_3)
A list of initialization statements is evaluated with the relation:
Click to view the specification source
relation ForInitStmts_eval: evalContext arch |- forInitStatementListIR : evalContext arch statementResult
An initialization statement is evaluated with the relation:
Click to view the specification source
relation ForInitStmt_eval: evalContext arch |- forInitStatementIR : evalContext arch statementResult
Click to view the specification source
rulegroup ForInitStmt_eval: rule ForInitStmt_eval/variableDeclarationIR-non-initializer: EC_0 ARCH |- annotationList typeIR nameIR eps : EC_1 ARCH `EMPTY -- if typeIR_subst = $subst_type_e(LOCAL, EC_0, typeIR) -- if value_init = $default(typeIR_subst) -- if EC_1 = $add_var_e(LOCAL, EC_0, ` nameIR, value_init) rule ForInitStmt_eval/variableDeclarationIR-initializer-abort: EC_0 ARCH_0 |- annotationList typeIR nameIR (= typedExpressionIR_init) : EC_1 ARCH_1 abortResult -- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR_init : EC_1 ARCH_1 abortResult rule ForInitStmt_eval/variableDeclarationIR-initializer-cont: EC_0 ARCH_0 |- annotationList typeIR nameIR (= typedExpressionIR_init) : EC_2 ARCH_1 `EMPTY -- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR_init : EC_1 ARCH_1 (` value_init) -- if EC_2 = $add_var_e(LOCAL, EC_1, ` nameIR, value_init) rule ForInitStmt_eval/forUpdateStatementIR: EC_0 ARCH_0 |- forUpdateStatementIR : EC_1 ARCH_1 statementResult -- ForUpdateStmt_eval: EC_0 ARCH_0 |- forUpdateStatementIR : EC_1 ARCH_1 statementResult
A list of update statements is evaluated with the relation:
Click to view the specification source
relation ForUpdateStmts_eval: evalContext arch |- forUpdateStatementListIR : evalContext arch statementResult
An update statement is evaluated with the relation:
Click to view the specification source
relation ForUpdateStmt_eval: evalContext arch |- forUpdateStatementIR : evalContext arch statementResult
Click to view the specification source
rulegroup ForUpdateStmt_eval: rule ForUpdateStmt_eval/call: EC_0 ARCH_0 |- callableTargetIR `<typeArgumentListIR> `(argumentListIR) : EC_1 ARCH_1 statementResult -- Stmt_eval: LOCAL EC_0 ARCH_0 |- callableTargetIR `<typeArgumentListIR> `(argumentListIR) ; : EC_1 ARCH_1 statementResult rule ForUpdateStmt_eval/assign: EC_0 ARCH_0 |- typedLvalueIR assignop typedExpressionIR : EC_1 ARCH_1 statementResult -- Stmt_eval: LOCAL EC_0 ARCH_0 |- typedLvalueIR assignop typedExpressionIR ; : EC_1 ARCH_1 statementResult
With these, the 3-clause for statement is evaluated with the relation:
Click to view the specification source
relation ForThreeClauseStmt_eval: evalContext arch |- typedExpressionIR statementIR forUpdateStatementListIR : evalContext arch statementResult
Click to view the specification source
rulegroup ForThreeClauseStmt_eval: rule ForThreeClauseStmt_eval/cond-abort: EC_0 ARCH_0 |- typedExpressionIR statementIR forUpdateStatementListIR : EC_1 ARCH_1 abortResult -- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 abortResult rule ForThreeClauseStmt_eval/cond-false: EC_0 ARCH_0 |- typedExpressionIR statementIR forUpdateStatementListIR : EC_1 ARCH_1 `EMPTY -- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 (` (`B false)) rule ForThreeClauseStmt_eval/cond-true-loop-abort: EC_0 ARCH_0 |- typedExpressionIR statementIR forUpdateStatementListIR : EC_2 ARCH_2 abortResult -- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 (` (`B true)) -- Stmt_eval: LOCAL EC_1 ARCH_1 |- statementIR : EC_2 ARCH_2 abortResult rule ForThreeClauseStmt_eval/cond-true-loop-return: EC_0 ARCH_0 |- typedExpressionIR statementIR forUpdateStatementListIR : EC_2 ARCH_2 returnResult -- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 (` (`B true)) -- Stmt_eval: LOCAL EC_1 ARCH_1 |- statementIR : EC_2 ARCH_2 returnResult rule ForThreeClauseStmt_eval/cond-true-loop-break: EC_0 ARCH_0 |- typedExpressionIR statementIR forUpdateStatementListIR : EC_2 ARCH_2 `EMPTY -- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 (` (`B true)) -- Stmt_eval: LOCAL EC_1 ARCH_1 |- statementIR : EC_2 ARCH_2 BREAK rule ForThreeClauseStmt_eval/cond-true-loop-cont-update-exit: EC_0 ARCH_0 |- typedExpressionIR statementIR forUpdateStatementListIR : EC_3 ARCH_3 EXIT -- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 (` (`B true)) -- Stmt_eval: LOCAL EC_1 ARCH_1 |- statementIR : EC_2 ARCH_2 statementResult_loop -- if statementResult_loop = `EMPTY \/ statementResult_loop = CONTINUE -- ForUpdateStmts_eval: EC_2 ARCH_2 |- forUpdateStatementListIR : EC_3 ARCH_3 EXIT rule ForThreeClauseStmt_eval/cond-true-loop-cont-update-cont: EC_0 ARCH_0 |- typedExpressionIR statementIR forUpdateStatementListIR : EC_4 ARCH_4 statementResult -- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 (` (`B true)) -- Stmt_eval: LOCAL EC_1 ARCH_1 |- statementIR : EC_2 ARCH_2 statementResult_loop -- if statementResult_loop = `EMPTY \/ statementResult_loop = CONTINUE -- ForUpdateStmts_eval: EC_2 ARCH_2 |- forUpdateStatementListIR : EC_3 ARCH_3 `EMPTY -- ForThreeClauseStmt_eval: EC_3 ARCH_3 |- typedExpressionIR statementIR forUpdateStatementListIR : EC_4 ARCH_4 statementResult
13.10.2. for-in statements
The for-in statement executes the body once for each value in a range or
each element in a list expression or header stack. The list or range
expression itself will only be evaluated once, before the first iteration of
the loop. All side effects in the list or range expression will occur before
the first iteration of the loop body.
13.10.2.1. Type checking
After type checking, a for-in statement has the form:
forStatementIR
: annotationList FOR
`( forInitStatementListIR ; typedExpressionIR ; forUpdateStatementListIR )
statementIR
| annotationList FOR `( typeIR nameIR IN forCollectionExpressionIR )
statementIR
| annotationList FOR
`( annotationList typeIR nameIR IN forCollectionExpressionIR )
statementIR
;
forCollectionExpressionIR
: typedExpressionIR
| typedExpressionIR .. typedExpressionIR
;
Click to view the specification source
rulegroup Stmt_ok/forStatement-in: rule Stmt_ok/no-annotations: LOCAL TC_0 f l |- annotationList FOR `(type name IN forCollectionExpression) statement : TC_0 f_post forStatementIR -- Type_ok: LOCAL TC_0 |- type : typeIR # eps -- Type_wf: $bound(LOCAL, TC_0) |- typeIR -- if $is_assignable_typeIR(typeIR) -- ForCollectionExpr_ok: TC_0 typeIR |- forCollectionExpression : forCollectionExpressionIR -- if TC_1 = $enter_t(TC_0) -- if nameIR = $name(name) -- if TC_2 = $add_var_t(LOCAL, TC_1, nameIR, INOUT typeIR DYN eps) -- Stmt_ok: LOCAL TC_2 f LOOP |- statement : TC_3 f_post statementIR -- if TC_4 = $exit_t(TC_3) -- if forStatementIR = annotationList FOR `(typeIR nameIR IN forCollectionExpressionIR) statementIR rule Stmt_ok/annotations: LOCAL TC_0 f l |- annotationList FOR `(annotationListNonEmpty type name IN forCollectionExpression) statement : TC_0 f_post forStatementIR -- Stmt_ok: LOCAL TC_0 f l |- annotationList FOR `(type name IN forCollectionExpression) statement : TC_1 f_post forStatementIR
Collection expressions are type checked with the relation:
Click to view the specification source
relation ForCollectionExpr_ok: typingContext typeIR |- forCollectionExpression : forCollectionExpressionIR
Click to view the specification source
rulegroup ForCollectionExpr_ok: rule ForCollectionExpr_ok/list: TC typeIR_var |- expression : typedExpressionIR -- Expr_ok: LOCAL TC |- expression : typedExpressionIR -- if typeIR_expression = $type_of_typedExpressionIR(typedExpressionIR) -- if LIST `<typeIR_e> = $unroll_typeIR(typeIR_expression) -- Cast_impl: typeIR_e -> typeIR_var rule ForCollectionExpr_ok/header-stack: TC typeIR_var |- expression : typedExpressionIR -- Expr_ok: LOCAL TC |- expression : typedExpressionIR -- if typeIR_expression = $type_of_typedExpressionIR(typedExpressionIR) -- if typeIR_e `[_] = $unroll_typeIR(typeIR_expression) -- Type_alpha: typeIR_e ~~ typeIR_var rule ForCollectionExpr_ok/range: TC typeIR_var |- expression_l .. expression_r : typedExpressionIR_l_casted .. typedExpressionIR_r_casted -- Expr_ok: LOCAL TC |- expression_l : typedExpressionIR_l -- Expr_ok: LOCAL TC |- expression_r : typedExpressionIR_r -- if typedExpressionIR_l_casted = $cast_unary(typedExpressionIR_l, typeIR_var) -- if typedExpressionIR_r_casted = $cast_unary(typedExpressionIR_r, typeIR_var) -- if typeIR_l = $type_of_typedExpressionIR(typedExpressionIR_l_casted) -- if typeIR_r = $type_of_typedExpressionIR(typedExpressionIR_r_casted) -- if $compat_range(typeIR_l, typeIR_r)
13.10.2.2. Compile-time evaluation
Click to view the specification source
rulegroup Stmt_inst/forStatementIR-in: rule Stmt_inst/no-annotations: p IC STO_0 |- annotationList FOR `(typeIR nameIR IN forCollectionExpressionIR) statementIR : IC STO_1 forStatementIR -- Stmt_inst: p IC STO_0 |- statementIR : IC_body STO_1 statementIR_body -- if forStatementIR = annotationList FOR `(typeIR nameIR IN forCollectionExpressionIR) statementIR_body rule Stmt_inst/annotations: p IC STO_0 |- annotationList FOR `(annotationList typeIR nameIR IN forCollectionExpressionIR) statementIR : IC STO_1 forStatementIR -- Stmt_inst: p IC STO_0 |- statementIR : IC_body STO_1 statementIR_body -- if forStatementIR = annotationList FOR `(annotationList typeIR nameIR IN forCollectionExpressionIR) statementIR_body
13.10.2.3. Runtime evaluation
Click to view the specification source
rulegroup Stmt_eval/forStatementIR-in: rule Stmt_eval/no-annotations-collection-abort: LOCAL EC_0 ARCH_0 |- annotationList FOR `(typeIR nameIR IN forCollectionExpressionIR) statementIR : EC_3 ARCH_1 abortResult -- if EC_1 = $enter_e(EC_0) -- ForCollectionExpr_eval: EC_1 ARCH_0 |- forCollectionExpressionIR : EC_2 ARCH_1 abortResult -- if EC_3 = $exit_e(EC_2) rule Stmt_eval/no-annotations-collection-loop: LOCAL EC_0 ARCH_0 |- annotationList FOR `(typeIR nameIR IN forCollectionExpressionIR) statementIR : EC_5 ARCH_2 statementResult -- if EC_1 = $enter_e(EC_0) -- ForCollectionExpr_eval: EC_1 ARCH_0 |- forCollectionExpressionIR : EC_2 ARCH_1 (` value*) -- if typeIR_subst = $subst_type_e(LOCAL, EC_0, typeIR) -- if value_init = $default(typeIR_subst) -- if EC_3 = $add_var_e(LOCAL, EC_2, ` nameIR, value_init) -- ForInStmt_eval: EC_3 ARCH_1 |- nameIR value* statementIR : EC_4 ARCH_2 statementResult -- if EC_5 = $exit_e(EC_4) rule Stmt_eval/annotations: LOCAL EC_0 ARCH_0 |- annotationList FOR `(annotationList_in typeIR nameIR IN forCollectionExpressionIR) statementIR : EC_1 ARCH_1 statementResult -- Stmt_eval: LOCAL EC_0 ARCH_0 |- annotationList FOR `(typeIR nameIR IN forCollectionExpressionIR) statementIR : EC_1 ARCH_1 statementResult
Collection expressions are evaluated with the relation:
Click to view the specification source
relation ForCollectionExpr_eval: evalContext arch |- forCollectionExpressionIR : evalContext arch expressionListResult
Click to view the specification source
rulegroup ForCollectionExpr_eval: rule ForCollectionExpr_eval/abort: EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 abortResult -- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 abortResult rule ForCollectionExpr_eval/list: EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 (` value*) -- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 (` listValue) -- if LIST `[value*] = listValue rule ForCollectionExpr_eval/header-stack: EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 (` value*) -- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 (` headerStackValue) -- if HEADER_STACK `[value* `(_ ; _)] = headerStackValue
With these, the for-in statement is evaluated with the relation:
Click to view the specification source
relation ForInStmt_eval: evalContext arch |- nameIR value* statementIR : evalContext arch statementResult
Click to view the specification source
rulegroup ForInStmt_eval: rule ForInStmt_eval/nil: EC ARCH |- nameIR eps statementIR : EC ARCH `EMPTY rule ForInStmt_eval/cons-loop-abort: EC_0 ARCH_0 |- nameIR (value_h :: value_t*) statementIR : EC_2 ARCH_1 abortResult -- if EC_1 = $update_var_e(LOCAL, EC_0, ` nameIR, value_h) -- Stmt_eval: LOCAL EC_1 ARCH_0 |- statementIR : EC_2 ARCH_1 abortResult rule ForInStmt_eval/cons-loop-return: EC_0 ARCH_0 |- nameIR (value_h :: value_t*) statementIR : EC_2 ARCH_1 returnResult -- if EC_1 = $update_var_e(LOCAL, EC_0, ` nameIR, value_h) -- Stmt_eval: LOCAL EC_1 ARCH_0 |- statementIR : EC_2 ARCH_1 returnResult rule ForInStmt_eval/cons-loop-break: EC_0 ARCH_0 |- nameIR (value_h :: value_t*) statementIR : EC_2 ARCH_1 `EMPTY -- if EC_1 = $update_var_e(LOCAL, EC_0, ` nameIR, value_h) -- Stmt_eval: LOCAL EC_1 ARCH_0 |- statementIR : EC_2 ARCH_1 BREAK rule ForInStmt_eval/cons-loop-cont: EC_0 ARCH_0 |- nameIR (value_h :: value_t*) statementIR : EC_3 ARCH_2 statementResult -- if EC_1 = $update_var_e(LOCAL, EC_0, ` nameIR, value_h) -- Stmt_eval: LOCAL EC_1 ARCH_0 |- statementIR : EC_2 ARCH_1 statementResult_loop -- if statementResult_loop = `EMPTY \/ statementResult_loop = CONTINUE -- ForInStmt_eval: EC_2 ARCH_1 |- nameIR value_t* statementIR : EC_3 ARCH_2 statementResult
13.11. Break statements
A break; statement exists the enclosing for loop.
breakStatement
: BREAK ;
;
13.11.1. Type checking
After type checking, a break; statement has the form:
breakStatementIR = breakStatement
Click to view the specification source
rulegroup Stmt_ok/breakStatement: rule Stmt_ok/breakStatement: LOCAL TC f LOOP |- breakStatement : TC f breakStatement
13.11.2. Compile-time evaluation
Click to view the specification source
rulegroup Stmt_inst/breakStatementIR: rule Stmt_inst/breakStatementIR: p IC STO |- breakStatementIR : IC STO breakStatementIR
13.11.3. Runtime evaluation
A break; statement yields the result:
forBreakResult
: BREAK
;
Click to view the specification source
rulegroup Stmt_eval/breakStatementIR: rule Stmt_eval/breakStatementIR: LOCAL EC ARCH |- BREAK ; : EC ARCH BREAK
13.12. Continue statements
A continue; statement skips the current iteration of the enclosing for
loop.
continueStatement
: CONTINUE ;
;
13.12.1. Type checking
After type checking, a continue; statement has the form:
continueStatementIR = continueStatement
Click to view the specification source
rulegroup Stmt_ok/continueStatement: rule Stmt_ok/continueStatement: LOCAL TC f LOOP |- continueStatement : TC f continueStatement
13.12.2. Compile-time evaluation
Click to view the specification source
rulegroup Stmt_inst/continueStatementIR: rule Stmt_inst/continueStatementIR: p IC STO |- continueStatementIR : IC STO continueStatementIR
13.12.3. Runtime evaluation
A continue; statement yields the result:
forContinueResult
: CONTINUE
;
Click to view the specification source
rulegroup Stmt_eval/continueStatementIR: rule Stmt_eval/continueStatementIR: LOCAL EC ARCH |- CONTINUE ; : EC ARCH CONTINUE
13.13. Switch statements
The switch statement can only be used within control blocks, action
bodies, or function bodies.
switchStatement
: SWITCH `( expression ) `{ switchCaseList }
;
switchCaseList
: /* empty */
| switchCaseList switchCase
;
switchCase
: switchLabel : blockStatement
| switchLabel :
;
switchLabel
: DEFAULT
| expressionNonBrace
;
The expressionNonBrace is the same as expression as defined in
Chapter 14, except it does not include any cases that can begin with a
left brace { character, to avoid syntactic ambiguity with a block statement.
There are two kinds of switch expressions allowed, described separately in
the following two subsections.
13.13.1. Notes common to all switch statements
It is a compile-time error if two labels of a switch statement equal each
other. The switch label values need not include all possible values of the
switch expression. It is optional to have a switch case with the default
label, but if one is present, it must be the last one in the switch
statement.
If a switch label is not followed by a block statement, it falls through to the
next label. However, if a block statement is present, it does not fall through.
Note that this is different from C-style switch statements, where a break
is needed to prevent fall-through. If the last switch label is not followed by
a block statement, the behavior is the same as if the last switch label were
followed by an empty block statement { }.
When a switch statement is executed, first the switch expression is
evaluated, and any side effects from evaluating this expression are visible to
any switch case that is executed. Among switch labels that are not default,
at most one of them can equal the value of the switch expression. If one is
equal, that switch case is executed.
If no labels are equal to the switch expression, then:
-
if there is a
defaultlabel, the case with thedefaultlabel is executed. -
if there is no
defaultlabel, then no switch case is executed, and execution continues after the end of theswitchstatement, with no side effects (except any that were caused by evaluating theswitchexpression).
See "Implementing generalized P4_16 switch statements" for possible techniques that one might use to implement generalized switch statements.[3]
13.13.2. Switch statement on match-action table result
This type of switch statement can only be used within control apply blocks.
For this variant of switch statement, the expression must be of the form
t.apply().action_run, where t is the name of a table (see
Section 16.3.7). All switch labels must be names of
actions of the table t, or default.
switch (t.apply().action_run) {
action1: // fall-through to action2:
action2: { /* body omitted */ }
action3: { /* body omitted */ } // no fall-through from action2 to action3 labels
default: { /* body omitted */ }
}
Note that the default label of the switch statement is used to match on the
kind of action executed, no matter whether there was a table hit or miss. The
default label does not indicate that the table missed and the
default_action was executed.
13.13.2.1. Type checking
Click to view the specification source
rulegroup Stmt_ok/switchStatement-table:
rule Stmt_ok/switchStatement-table:
LOCAL TC f l |- SWITCH `(expression_switch) `{switchCaseList} : TC f_post switchStatementIR
-- Expr_ok: LOCAL TC |- expression_switch : typedExpressionIR_switch
-- if typeIR_switch = $type_of_typedExpressionIR(typedExpressionIR_switch)
-- if TABLE_ENUM typeId_table_enum `{_} = $unroll_typeIR(typeIR_switch)
-- if typeId_table = $strip_prefix($strip_suffix(typeId_table_enum, ")"), "action_list(")
-- SwitchCaseList_table_ok: TC f l typeId_table |- switchCaseList : f_post switchCaseIR* # switchLabel*
-- if $check_switchLabel_default(switchLabel*)
-- if $distinct_<switchLabel>(switchLabel*)
-- if switchStatementIR = SWITCH `(typedExpressionIR_switch) `{switchCaseIR*}
Switch cases are type checked with the relation:
Click to view the specification source
relation SwitchCaseList_table_ok: typingContext flow loopctxt typeId |- switchCaseList : flow switchCaseListIR # switchLabel*
A switch case is type checked with:
Click to view the specification source
relation SwitchCase_table_ok: typingContext flow loopctxt typeId |- switchCase : flow switchCaseIR # switchLabel
Click to view the specification source
rulegroup SwitchCase_table_ok: rule SwitchCase_table_ok/blockStatement: TC f l typeId_table |- switchLabel : blockStatement : f_post switchCaseIR # switchLabel -- SwitchLabel_table_ok: TC typeId_table |- switchLabel : switchLabelIR -- Block_ok: TC f l |- blockStatement : TC_post f_post blockStatementIR -- if switchCaseIR = switchLabelIR : blockStatementIR rule SwitchCase_table_ok/non-blockStatement: TC f l typeId_table |- switchLabel : : f switchCaseIR # switchLabel -- SwitchLabel_table_ok: TC typeId_table |- switchLabel : switchLabelIR -- if switchCaseIR = switchLabelIR :
A switch label is type checked with:
Click to view the specification source
relation SwitchLabel_table_ok: typingContext typeId |- switchLabel : switchLabelIR
Click to view the specification source
rulegroup SwitchLabel_table_ok:
rule SwitchLabel_table_ok/default:
TC typeId_table |- DEFAULT : DEFAULT
rule SwitchLabel_table_ok/expressionNonBrace-prefixedNonTypeName:
TC typeId_table |- prefixedNonTypeName : typedExpressionIR_label
-- if ` nameIR_label = $prefixedNonTypeName(prefixedNonTypeName)
-- if typeId_table_enum = $concat_text(["action_list(", typeId_table, ")"])
-- if id_label = $concat_text([typeId_table_enum, ".", nameIR_label])
-- if _ typeIR_label ctk_label value_label = $find_var_t(` id_label, LOCAL, TC)
-- if value_label = TABLE_ENUM typeId_table_enum . nameIR_label
-- if typedExpressionIR_label = (` nameIR_label) # `(typeIR_label ctk_label)
13.13.2.2. Compile-time evaluation
Click to view the specification source
rulegroup Stmt_inst/switchStatementIR:
rule Stmt_inst/switchStatementIR:
p IC STO_0 |- SWITCH `(typedExpressionIR) `{switchCaseListIR} : IC STO_1 (SWITCH `(typedExpressionIR) `{switchCaseListIR_inst})
-- SwitchCases_inst: p IC STO_0 |- switchCaseListIR : STO_1 switchCaseListIR_inst
The compile-time evaluation of switch cases is defined with the relation:
Click to view the specification source
relation SwitchCases_inst: cursor instContext store |- switchCaseIR* : store switchCaseIR*
A switch case is compile-time evaluated with:
Click to view the specification source
relation SwitchCase_inst: cursor instContext store |- switchCaseIR : store switchCaseIR
Click to view the specification source
rulegroup SwitchCase_inst: rule SwitchCase_inst/blockStatementIR: p IC STO_0 |- switchLabelIR : blockStatementIR : STO_1 (switchLabelIR : blockStatementIR_inst) -- Stmt_inst: p IC STO_0 |- blockStatementIR : IC_switch STO_1 blockStatementIR_inst rule SwitchCase_inst/non-blockStatementIR: p IC STO |- switchLabelIR : : STO (switchLabelIR :)
13.13.2.3. Runtime evaluation
Click to view the specification source
rulegroup Stmt_eval/switchStatementIR-table:
rule Stmt_eval/target-abort:
LOCAL EC_0 ARCH_0 |- SWITCH `(typedExpressionIR) `{switchCaseIR*} : EC_1 ARCH_1 abortResult
-- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 abortResult
rule Stmt_eval/target-cont-match:
LOCAL EC_0 ARCH_0 |- SWITCH `(typedExpressionIR) `{switchCaseIR*} : EC_2 ARCH_2 statementResult
-- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 (` value)
-- if TABLE_ENUM _ . id_enum = value
-- SwitchCases_table_eval: id_enum |- switchCaseIR* : blockStatementIR
-- Stmt_eval: LOCAL EC_1 ARCH_1 |- blockStatementIR : EC_2 ARCH_2 statementResult
rule Stmt_eval/target-cont-no-match:
LOCAL EC_0 ARCH_0 |- SWITCH `(typedExpressionIR) `{switchCaseIR*} : EC_1 ARCH_1 `EMPTY
-- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 (` value)
-- if TABLE_ENUM _ . id_enum = value
-- SwitchCases_table_eval: id_enum |- switchCaseIR* : eps
Switch cases are runtime evaluated with the relation:
Click to view the specification source
relation SwitchCases_table_eval: id |- switchCaseListIR : blockStatementIR?
Click to view the specification source
rulegroup SwitchCases_table_eval: rule SwitchCases_table_eval/nil: id |- eps : eps rule SwitchCases_table_eval/cons-non-fallthrough-match: id |- switchCaseIR_h :: switchCaseIR_t* : blockStatementIR_h -- if switchLabelIR_h : blockStatementIR_h = switchCaseIR_h -- if $match_switchLabelIR_table(id, switchLabelIR_h) rule SwitchCases_table_eval/cons-non-fallthrough-no-match: id |- switchCaseIR_h :: switchCaseIR_t* : blockStatementIR? -- if switchLabelIR_h : blockStatementIR_h = switchCaseIR_h -- if ~$match_switchLabelIR_table(id, switchLabelIR_h) -- SwitchCases_table_eval: id |- switchCaseIR_t* : blockStatementIR? rule SwitchCases_table_eval/cons-fallthrough-match: id |- switchCaseIR_h :: switchCaseIR_t* : blockStatementIR -- if switchLabelIR_h : = switchCaseIR_h -- if $match_switchLabelIR_table(id, switchLabelIR_h) -- if (_ : blockStatementIR) :: _ = $filter_<switchCaseIR>(switchCaseIR_t*, $is_non_fallthrough_switchCaseIR(switchCaseIR_t)*) rule SwitchCases_table_eval/cons-fallthrough-no-match: id |- switchCaseIR_h :: switchCaseIR_t* : blockStatementIR? -- if switchLabelIR_h : = switchCaseIR_h -- if ~$match_switchLabelIR_table(id, switchLabelIR_h) -- SwitchCases_table_eval: id |- switchCaseIR_t* : blockStatementIR?
13.13.3. Switch statement on numeric or enumerated type
For this variant of switch statement, the expression must evaluate to a
result with one of these types:
-
bit<W> -
int<W> -
enum, either with or without an underlying representation specified -
error
All switch labels must be expressions with compile-time known values, and must
have a type that can be implicitly cast to the type of the switch expression
(see [sec-implicit-casts]). Switch labels must not begin with a left brace
character {, to avoid ambiguity with a block statement.
// Assume the expression hdr.ethernet.etherType has type bit<16>.
switch (hdr.ethernet.etherType) {
0x86dd: { /* body omitted */ }
0x0800: // fall-through to the next body
0x0802: { /* body omitted */ }
0xcafe: { /* body omitted */ }
default: { /* body omitted */ }
}
13.13.3.1. Type checking
Click to view the specification source
rulegroup Stmt_ok/switchStatement-general:
rule Stmt_ok/switchStatement-general:
LOCAL TC f l |- SWITCH `(expression_switch) `{switchCaseList} : TC f_post switchStatementIR
-- Expr_ok: LOCAL TC |- expression_switch : typedExpressionIR_switch
-- if typeIR_switch = $type_of_typedExpressionIR(typedExpressionIR_switch)
-- if $compat_switch(typeIR_switch)
-- SwitchCaseList_general_ok: TC f l typeIR_switch |- switchCaseList : f_post switchCaseIR* # switchLabel*
-- if $check_switchLabel_default(switchLabel*)
-- if $distinct_<switchLabel>(switchLabel*)
-- if switchStatementIR = SWITCH `(typedExpressionIR_switch) `{switchCaseIR*}
Switch cases are type checked with the relation:
Click to view the specification source
relation SwitchCaseList_general_ok: typingContext flow loopctxt typeIR |- switchCaseList : flow switchCaseListIR # switchLabel*
A switch case is type checked with:
Click to view the specification source
relation SwitchCase_general_ok: typingContext flow loopctxt typeIR |- switchCase : flow switchCaseIR # switchLabel
Click to view the specification source
rulegroup SwitchCase_general_ok: rule SwitchCase_general_ok/blockStatement: TC f l typeIR_switch |- switchLabel : blockStatement : f_post switchCaseIR # switchLabel -- SwitchLabel_general_ok: TC typeIR_switch |- switchLabel : switchLabelIR -- Block_ok: TC f l |- blockStatement : TC_post f_post blockStatementIR -- if switchCaseIR = switchLabelIR : blockStatementIR rule SwitchCase_general_ok/non-blockStatement: TC f l typeIR_switch |- switchLabel : : f switchCaseIR # switchLabel -- SwitchLabel_general_ok: TC typeIR_switch |- switchLabel : switchLabelIR -- if switchCaseIR = switchLabelIR :
A switch label is type checked with:
Click to view the specification source
relation SwitchLabel_general_ok: typingContext typeIR |- switchLabel : switchLabelIR
Click to view the specification source
rulegroup SwitchLabel_general_ok: rule SwitchLabel_general_ok/default: TC _ |- DEFAULT : DEFAULT rule SwitchLabel_general_ok/expressionNonBrace: TC typeIR_switch |- expressionNonBrace_label : typedExpressionIR_label_cast -- if expression_label = $expressionNonBrace_as_expression(expressionNonBrace_label) -- Expr_ok: LOCAL TC |- expression_label : typedExpressionIR_label -- if typedExpressionIR_label_cast = $cast_unary(typedExpressionIR_label, typeIR_switch) -- if _ # `(_ LCTK) = typedExpressionIR_label_cast
13.13.3.2. Compile-time evaluation
See Section 13.13.2.2.
13.13.3.3. Runtime evaluation
Click to view the specification source
rulegroup Stmt_eval/switchStatementIR-general:
rule Stmt_eval/target-abort:
LOCAL EC_0 ARCH_0 |- SWITCH `(typedExpressionIR) `{switchCaseIR*} : EC_1 ARCH_1 abortResult
-- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 abortResult
rule Stmt_eval/target-cont-match:
LOCAL EC_0 ARCH_0 |- SWITCH `(typedExpressionIR) `{switchCaseIR*} : EC_3 ARCH_3 statementResult
-- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 (` value)
-- SwitchCases_general_eval: LOCAL EC_1 ARCH_1 value |- switchCaseIR* : EC_2 ARCH_2 blockStatementIR
-- Stmt_eval: LOCAL EC_2 ARCH_2 |- blockStatementIR : EC_3 ARCH_3 statementResult
rule Stmt_eval/target-cont-no-match:
LOCAL EC_0 ARCH_0 |- SWITCH `(typedExpressionIR) `{switchCaseIR*} : EC_2 ARCH_2 `EMPTY
-- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 (` value)
-- SwitchCases_general_eval: LOCAL EC_1 ARCH_1 value |- switchCaseIR* : EC_2 ARCH_2 eps
Switch cases are runtime evaluated with the relation:
Click to view the specification source
relation SwitchCases_general_eval: cursor evalContext arch value |- switchCaseListIR : evalContext arch blockStatementIR?
Click to view the specification source
rulegroup SwitchCases_general_eval: rule SwitchCases_general_eval/nil: LOCAL EC_0 ARCH_0 value |- eps : EC_0 ARCH_0 eps rule SwitchCases_general_eval/cons-non-fallthrough-match: LOCAL EC_0 ARCH_0 value |- (switchCaseIR_h :: switchCaseIR_t*) : EC_1 ARCH_1 blockStatementIR_h -- if switchLabelIR_h : blockStatementIR_h = switchCaseIR_h -- SwitchLabel_general_eval: LOCAL EC_0 ARCH_0 |- switchLabelIR_h : EC_1 ARCH_1 (` value_label) -- if $match_switchLabelIR_general(value, value_label) rule SwitchCases_general_eval/cons-non-fallthrough-no-match: LOCAL EC_0 ARCH_0 value |- (switchCaseIR_h :: switchCaseIR_t*) : EC_2 ARCH_2 blockStatementIR? -- if switchLabelIR_h : blockStatementIR_h = switchCaseIR_h -- SwitchLabel_general_eval: LOCAL EC_0 ARCH_0 |- switchLabelIR_h : EC_1 ARCH_1 (` value_label) -- if ~$match_switchLabelIR_general(value, value_label) -- SwitchCases_general_eval: LOCAL EC_1 ARCH_1 value |- switchCaseIR_t* : EC_2 ARCH_2 blockStatementIR? rule SwitchCases_general_eval/cons-fallthrough-match: LOCAL EC_0 ARCH_0 value |- (switchCaseIR_h :: switchCaseIR_t*) : EC_1 ARCH_1 blockStatementIR -- if switchLabelIR_h : = switchCaseIR_h -- SwitchLabel_general_eval: LOCAL EC_0 ARCH_0 |- switchLabelIR_h : EC_1 ARCH_1 (` value_label) -- if $match_switchLabelIR_general(value, value_label) -- if (_ : blockStatementIR) :: _ = $filter_<switchCaseIR>(switchCaseIR_t*, $is_non_fallthrough_switchCaseIR(switchCaseIR_t)*) rule SwitchCases_general_eval/cons-fallthrough-no-match: LOCAL EC_0 ARCH_0 value |- (switchCaseIR_h :: switchCaseIR_t*) : EC_2 ARCH_2 blockStatementIR? -- if switchLabelIR_h : = switchCaseIR_h -- SwitchLabel_general_eval: LOCAL EC_0 ARCH_0 |- switchLabelIR_h : EC_1 ARCH_1 (` value_label) -- if ~$match_switchLabelIR_general(value, value_label) -- SwitchCases_general_eval: LOCAL EC_1 ARCH_1 value |- switchCaseIR_t* : EC_2 ARCH_2 blockStatementIR?
A switch label is runtime evaluated with:
Click to view the specification source
relation SwitchLabel_general_eval: cursor evalContext arch |- switchLabelIR : evalContext arch expressionResult
Click to view the specification source
rulegroup SwitchLabel_general_eval: rule SwitchLabel_general_eval/default: LOCAL EC_0 ARCH_0 |- DEFAULT : EC_0 ARCH_0 (` DEFAULT) rule SwitchLabel_general_eval/typedExpressionIR: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 expressionResult -- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 expressionResult
14. Expressions
The syntax of expressions is defined as follows:
expression
: literalExpression
| referenceExpression
| defaultExpression
| unaryExpression
| binaryExpression
| ternaryExpression
| castExpression
| dataExpression
| accessExpression
| callExpression
| parenthesizedExpression
;
14.1. Semantics of expressions
Given a compound expression, the order in which sub-expressions are evaluated is important when the sub-expressions have side-effects. P4 expressions are evaluated as follows:
-
Boolean operators
&&and||use short-circuit evaluation—i.e., the second operand is only evaluated if necessary. -
The conditional operator
e1 ? e2 : e3evaluatese1, and then either evaluatese2ore3. -
All other expressions are evaluated left-to-right as they appear in the source program.
-
Method and function calls are evaluated as described in Section 18.4.
14.1.1. Type checking
Click to view the specification source
relation Expr_ok: cursor typingContext |- expression : typedExpressionIR
After type checking, expressions are represented in P4IR as follows:
typedExpressionIR
: expressionIR # expressionNoteIR
;
expressionIR
: literalExpressionIR
| referenceExpressionIR
| defaultExpressionIR
| unaryExpressionIR
| binaryExpressionIR
| ternaryExpressionIR
| castExpressionIR
| dataExpressionIR
| accessExpressionIR
| callExpressionIR
| parenthesizedExpressionIR
;
expressionNoteIR
: `( typeIR ctk )
;
Notice that after type checking, expressions in P4 are annotated with their types and their compile-time known-ness. See Section 7.5 for more details of compile-time known and local compile-time known values.
The following helper functions are used to fetch the type and compile-time known-ness of an expression:
Click to view the specification source
def $type_of_typedExpressionIR(typedExpressionIR) = typeIR -- if _ # `(typeIR _) = typedExpressionIR
Click to view the specification source
def $ctk_of_typedExpressionIR(typedExpressionIR) = ctk -- if _ # `(_ ctk) = typedExpressionIR
14.1.2. Local compile-time evaluation
Click to view the specification source
relation Expr_eval_lctk: cursor typingContext |- typedExpressionIR ~> value
Local compile-time known expressions are evaluated with the above relation.
14.1.3. Compile-time evaluation
Click to view the specification source
relation Expr_inst: cursor instContext store |- typedExpressionIR : store value
Compile-time known expressions are evaluated with the above relation. A sequence of expressions can be evaluated with the following relation:
Click to view the specification source
relation Exprs_inst: cursor instContext store |- typedExpressionIR* : store value*
A list of expressions is evaluated with the following relation:
Click to view the specification source
relation Exprs_inst: cursor instContext store |- typedExpressionIR* : store value*
Click to view the specification source
rulegroup Exprs_inst: rule Exprs_inst/nil: p IC STO |- eps : STO eps rule Exprs_inst/cons: p IC STO_0 |- typedExpressionIR_h :: typedExpressionIR_t* : STO_2 (value_h :: value_t*) -- Expr_inst: p IC STO_0 |- typedExpressionIR_h : STO_1 value_h -- Exprs_inst: p IC STO_1 |- typedExpressionIR_t* : STO_2 value_t*
14.1.4. Runtime evaluation
Click to view the specification source
relation Expr_eval: cursor evalContext arch |- typedExpressionIR : evalContext arch expressionResult
The result of evaluating an expression is represented as follows:
expressionResult
: continueResult<value>
| abortResult
;
continueResult<X>
: ` X
;
abortResult
: exitResult
| rejectTransitionResult
;
exitResult
: EXIT
;
rejectTransitionResult
: REJECT errorValue
;
See Section 13.7 and [sec-packet] for more details on exit and reject results.
A list of expressions can be evaluated with the following relation:
Click to view the specification source
relation Exprs_eval: cursor evalContext arch |- typedExpressionIR* : evalContext arch expressionListResult
The result of evaluating a list of expressions is represented as follows:
expressionListResult
: continueResult<value*>
| abortResult
;
Click to view the specification source
rulegroup Exprs_eval: rule Exprs_eval/nil: p EC ARCH |- eps : EC ARCH (` eps) rule Exprs_eval/cons-head-abort: p EC_0 ARCH_0 |- typedExpressionIR_h :: typedExpressionIR_t* : EC_1 ARCH_1 abortResult -- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_h : EC_1 ARCH_1 abortResult rule Exprs_eval/cons-head-cont-tail-abort: p EC_0 ARCH_0 |- typedExpressionIR_h :: typedExpressionIR_t* : EC_2 ARCH_2 abortResult -- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_h : EC_1 ARCH_1 (` value_h) -- Exprs_eval: p EC_1 ARCH_1 |- typedExpressionIR_t* : EC_2 ARCH_2 abortResult rule Exprs_eval/cons-head-cont-tail-cont: p EC_0 ARCH_0 |- typedExpressionIR_h :: typedExpressionIR_t* : EC_2 ARCH_2 (` (value_h :: value_t*)) -- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_h : EC_1 ARCH_1 (` value_h) -- Exprs_eval: p EC_1 ARCH_1 |- typedExpressionIR_t* : EC_2 ARCH_2 (` value_t*)
14.2. Literal expressions
Literal expressions denote fixed values of boolean, numeric, or string types:
literalExpression
: booleanLiteral
| integerLiteral
| stringLiteral
;
booleanLiteral
: TRUE
| FALSE
;
integerLiteral
: D int
| nat W int
| nat S int
;
stringLiteral
: " text "
;
14.2.1. Type checking
After type checking, literal expressions have the form:
literalExpressionIR = literalExpression
Click to view the specification source
rulegroup Expr_ok/literalExpression: rule Expr_ok/booleanLiteral: p TC |- booleanLiteral : booleanLiteral # expressionNoteIR -- if expressionNoteIR = `(BOOL LCTK) rule Expr_ok/integerLiteral-arbint: p TC |- D i : (D i) # expressionNoteIR -- if expressionNoteIR = `(INT LCTK) rule Expr_ok/integerLiteral-fixint: p TC |- n S i : (n S i) # expressionNoteIR -- if expressionNoteIR = `((INT `<n>) LCTK) rule Expr_ok/integerLiteral-fixbit: p TC |- n W i : (n W i) # expressionNoteIR -- if expressionNoteIR = `((BIT `<n>) LCTK) rule Expr_ok/stringLiteral: p TC |- stringLiteral : stringLiteral # expressionNoteIR -- if expressionNoteIR = `(STRING LCTK)
14.2.2. Local compile-time evaluation
Click to view the specification source
rulegroup Expr_eval_lctk/literalExpressionIR: rule Expr_eval_lctk/booleanLiteral: p TC |- booleanLiteral # _ ~> boolValue -- if boolValue = $ite<boolValue>(booleanLiteral = TRUE, `B true, `B false) rule Expr_eval_lctk/integerLiteral: p TC |- integerLiteral # _ ~> integerLiteral rule Expr_eval_lctk/stringLiteral: p TC |- stringLiteral # _ ~> stringLiteral
14.2.3. Compile-time evaluation
Click to view the specification source
rulegroup Expr_inst/literalExpressionIR: rule Expr_inst/booleanLiteral: p IC STO |- booleanLiteral # _ : STO boolValue -- if boolValue = $ite<boolValue>(booleanLiteral = TRUE, `B true, `B false) rule Expr_inst/integerLiteral: p IC STO |- integerLiteral # _ : STO integerLiteral rule Expr_inst/stringLiteral: p IC STO |- stringLiteral # _ : STO stringLiteral
14.2.4. Runtime evaluation
Click to view the specification source
rulegroup Expr_eval/literalExpressionIR: rule Expr_eval/booleanLiteral: p EC ARCH |- booleanLiteral # _ : EC ARCH expressionResult -- if boolValue = $ite<boolValue>(booleanLiteral = TRUE, `B true, `B false) -- if expressionResult = ` boolValue rule Expr_eval/integerLiteral: p EC ARCH |- integerLiteral # _ : EC ARCH expressionResult -- if expressionResult = ` integerLiteral rule Expr_eval/stringLiteral: p EC ARCH |- stringLiteral # _ : EC ARCH expressionResult -- if expressionResult = ` stringLiteral
14.3. Reference expressions
A reference expression is an expression that refers to a named entity in a P4 program:
referenceExpression
: prefixedNonTypeName
| THIS
;
prefixedNonTypeName
: nonTypeName
| `ID . nonTypeName
;
References are resolved according to the scoping rules of P4 (see Section 6.3).
14.3.1. Type checking
After type checking, a reference expression is represented in P4IR as:
referenceExpressionIR = prefixedNameIR
Click to view the specification source
rulegroup Expr_ok/referenceExpression: rule Expr_ok/prefixedNonTypeName: p TC |- prefixedNonTypeName : prefixedNameIR # expressionNoteIR -- if prefixedNameIR = $prefixedNonTypeName(prefixedNonTypeName) -- if _ typeIR ctk _ = $find_var_t(prefixedNameIR, p, TC) -- if expressionNoteIR = `(typeIR ctk) rule Expr_ok/this: p TC |- THIS : prefixedNameIR # expressionNoteIR -- if prefixedNameIR = ` "this" -- if _ typeIR ctk _ = $find_var_t(prefixedNameIR, p, TC) -- if expressionNoteIR = `(typeIR ctk)
A variable is looked up in the typing context with:
Click to view the specification source
def $find_var_t(. id, p, TC) = $find_map<id, varTypeIR>(typeFrame, id) -- if typeFrame = TC.GLOBAL.FRAME def $find_var_t(` id, GLOBAL, TC) = $find_map<id, varTypeIR>(typeFrame, id) -- if typeFrame = TC.GLOBAL.FRAME def $find_var_t(` id, BLOCK, TC) = varTypeIR -- if typeFrame = TC.BLOCK.FRAME -- if varTypeIR = $find_map<id, varTypeIR>(typeFrame, id) def $find_var_t(` id, BLOCK, TC) = $find_var_t(` id, GLOBAL, TC) -- if typeFrame = TC.BLOCK.FRAME -- if eps = $find_map<id, varTypeIR>(typeFrame, id) def $find_var_t(` id, LOCAL, TC) = varTypeIR -- if typeFrame* = TC.LOCAL.FRAMES -- if varTypeIR = $find_maps<id, varTypeIR>(typeFrame*, id) def $find_var_t(` id, LOCAL, TC) = $find_var_t(` id, BLOCK, TC) -- if typeFrame* = TC.LOCAL.FRAMES -- if eps = $find_maps<id, varTypeIR>(typeFrame*, id)
14.3.2. Local compile-time evaluation
Click to view the specification source
rulegroup Expr_eval_lctk/referenceExpressionIR: rule Expr_eval_lctk/referenceExpressionIR: p TC |- prefixedNameIR # _ ~> value -- if value = $find_var_value_t(prefixedNameIR, p, TC)
14.3.3. Compile-time evaluation
Click to view the specification source
rulegroup Expr_inst/referenceExpressionIR: rule Expr_inst/referenceExpressionIR: p IC STO |- prefixedNameIR # _ : STO value -- if value = $find_var_i(prefixedNameIR, p, IC)
A variable is looked up in the instantiation context with:
Click to view the specification source
def $find_var_i(. id, p, IC) = value -- if value = $find_map<id, value>(IC.GLOBAL.FRAME, id) def $find_var_i(` id, GLOBAL, IC) = value -- if value = $find_map<id, value>(IC.GLOBAL.FRAME, id) def $find_var_i(` id, BLOCK, IC) = value -- if value = $find_map<id, value>(IC.BLOCK.FRAME, id) def $find_var_i(` id, BLOCK, IC) = $find_var_i(` id, GLOBAL, IC) -- if eps = $find_map<id, value>(IC.BLOCK.FRAME, id) def $find_var_i(` id, LOCAL, IC) = value -- if value = $find_maps<id, value>(IC.LOCAL.FRAMES, id) def $find_var_i(` id, LOCAL, IC) = $find_var_i(` id, BLOCK, IC) -- if eps = $find_maps<id, value>(IC.LOCAL.FRAMES, id)
14.3.4. Runtime evaluation
Click to view the specification source
rulegroup Expr_eval/referenceExpressionIR: rule Expr_eval/referenceExpressionIR: p EC ARCH |- prefixedNameIR # _ : EC ARCH expressionResult -- if value = $find_var_e(prefixedNameIR, p, EC) -- if expressionResult = ` value
A variable is looked up in the runtime context with:
Click to view the specification source
def $find_var_e(. id, p, EC) = value -- if value = $find_map<id, value>(EC.GLOBAL.FRAME, id) def $find_var_e(` id, GLOBAL, EC) = value -- if value = $find_map<id, value>(EC.GLOBAL.FRAME, id) def $find_var_e(` id, BLOCK, EC) = value -- if value = $find_map<id, value>(EC.BLOCK.FRAME, id) def $find_var_e(` id, BLOCK, EC) = $find_var_e(` id, GLOBAL, EC) -- if eps = $find_map<id, value>(EC.BLOCK.FRAME, id) def $find_var_e(` id, LOCAL, EC) = value -- if value = $find_maps<id, value>(EC.LOCAL.FRAMES, id) def $find_var_e(` id, LOCAL, EC) = $find_var_e(` id, BLOCK, EC) -- if eps = $find_maps<id, value>(EC.LOCAL.FRAMES, id)
14.4. Default expressions
Default expressions provide a way to specify a default value for a type:
defaultExpression
: ...
;
See Section 8.6.1 for more details on default types. When default expressions are cast to a specific type, they evaluate to the default value for that type. See Section 19.3 for more details on default values.
14.4.1. Type checking
After type checking, default expressions have the form:
defaultExpressionIR = defaultExpression
Click to view the specification source
rulegroup Expr_ok/defaultExpression: rule Expr_ok/defaultExpression: p TC |- ... : ... # expressionNoteIR -- if expressionNoteIR = `(DEFAULT LCTK)
14.4.2. Local compile-time evaluation
Click to view the specification source
rulegroup Expr_eval_lctk/defaultExpressionIR: rule Expr_eval_lctk/defaultExpressionIR: p TC |- ... # _ ~> DEFAULT
14.4.3. Compile-time evaluation
Click to view the specification source
rulegroup Expr_inst/defaultExpressionIR: rule Expr_inst/defaultExpressionIR: p IC STO |- ... # _ : STO DEFAULT
14.4.4. Runtime evaluation
Click to view the specification source
rulegroup Expr_eval/defaultExpressionIR: rule Expr_eval/defaultExpressionIR: p EC ARCH |- ... # _ : EC ARCH expressionResult -- if expressionResult = ` DEFAULT
14.5. Unary expressions
unaryExpression
: unop expression
;
unop
: !
| ~
| -
| +
;
See Section 19.1 for details on how each unary operator is evaluated.
14.5.1. Type checking
After type checking, unary expressions have the form:
unaryExpressionIR
: unop typedExpressionIR
;
14.5.1.1. Negation
Click to view the specification source
rulegroup Expr_ok/unaryExpression-lnot: rule Expr_ok/unaryExpression-lnot: p TC |- ! expression : (! typedExpressionIR_reduced) # expressionNoteIR -- Expr_ok: p TC |- expression : typedExpressionIR -- if typedExpressionIR_reduced = $reduce_serenum_unary(typedExpressionIR, $compat_lnot) -- if typeIR_reduced = $type_of_typedExpressionIR(typedExpressionIR_reduced) -- if ctk_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_reduced) -- if expressionNoteIR = `(typeIR_reduced ctk_reduced)
14.5.1.2. Bitwise complement
Click to view the specification source
rulegroup Expr_ok/unaryExpression-bnot: rule Expr_ok/unaryExpression-bnot: p TC |- ~ expression : (~ typedExpressionIR_reduced) # expressionNoteIR -- Expr_ok: p TC |- expression : typedExpressionIR -- if typedExpressionIR_reduced = $reduce_serenum_unary(typedExpressionIR, $compat_bnot) -- if typeIR_reduced = $type_of_typedExpressionIR(typedExpressionIR_reduced) -- if ctk_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_reduced) -- if expressionNoteIR = `(typeIR_reduced ctk_reduced)
14.5.1.3. Plus and minus
Click to view the specification source
rulegroup Expr_ok/unaryExpression-uplusminus: rule Expr_ok/unaryExpression-uplusminus: p TC |- unop expression : (unop typedExpressionIR_reduced) # expressionNoteIR -- if unop = + \/ unop = - -- Expr_ok: p TC |- expression : typedExpressionIR -- if typedExpressionIR_reduced = $reduce_serenum_unary(typedExpressionIR, $compat_uplusminus) -- if typeIR_reduced = $type_of_typedExpressionIR(typedExpressionIR_reduced) -- if ctk_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_reduced) -- if expressionNoteIR = `(typeIR_reduced ctk_reduced)
14.5.2. Local compile-time evaluation
Click to view the specification source
rulegroup Expr_eval_lctk/unaryExpressionIR: rule Expr_eval_lctk/unaryExpressionIR: p TC |- (unop typedExpressionIR) # _ ~> $un_op(unop, value) -- Expr_eval_lctk: p TC |- typedExpressionIR ~> value
14.5.3. Compile-time evaluation
Click to view the specification source
rulegroup Expr_inst/unaryExpressionIR: rule Expr_inst/unaryExpressionIR: p IC STO_0 |- (unop typedExpressionIR) # _ : STO_1 $un_op(unop, value) -- Expr_inst: p IC STO_0 |- typedExpressionIR : STO_1 value
14.5.4. Runtime evaluation
Click to view the specification source
rulegroup Expr_eval/unaryExpressionIR: rule Expr_eval/abort: p EC_0 ARCH_0 |- (unop typedExpressionIR) # _ : EC_1 ARCH_1 abortResult -- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 abortResult rule Expr_eval/cont: p EC_0 ARCH_0 |- (unop typedExpressionIR) # _ : EC_1 ARCH_1 expressionResult -- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 (` value) -- if expressionResult = ` $un_op(unop, value)
14.6. Binary expressions
binaryExpression
: expression binop expression
;
binop
: *
| /
| %
| +
| -
| |+|
| |-|
| <<
| >>
| <=
| >=
| <
| >
| !=
| ==
| &
| ^
| |
| ++
| &&
| ||
;
See Section 19.2 for details on how each binary operator is evaluated. Note that logical operators (&&, ||) use short-circuit evaluation.
14.6.1. Type checking
14.6.1.1. Plus, minus, and multiplication
Click to view the specification source
rulegroup Expr_ok/binaryExpression-plusminusmult: rule Expr_ok/binaryExpression-plusminusmult: p TC |- expression_l binop expression_r : (typedExpressionIR_l_reduced binop typedExpressionIR_r_reduced) # expressionNoteIR -- if binop <- [+, -, *] -- Expr_ok: p TC |- expression_l : typedExpressionIR_l -- Expr_ok: p TC |- expression_r : typedExpressionIR_r -- if (typedExpressionIR_l_cast, typedExpressionIR_r_cast) = $cast_binary(typedExpressionIR_l, typedExpressionIR_r) -- if (typedExpressionIR_l_reduced, typedExpressionIR_r_reduced) = $reduce_serenum_binary(typedExpressionIR_l_cast, typedExpressionIR_r_cast, $compat_plusminusmult) -- if typeIR_reduced = $type_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if ctk_l_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if ctk_r_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_r_reduced) -- if ctk_reduced = $join_ctk(ctk_l_reduced, ctk_r_reduced) -- if expressionNoteIR = `(typeIR_reduced ctk_reduced)
14.6.1.2. Saturating plus and minus
Click to view the specification source
rulegroup Expr_ok/binaryExpression-satplusminus: rule Expr_ok/binaryExpression-satplusminus: p TC |- expression_l binop expression_r : (typedExpressionIR_l_reduced binop typedExpressionIR_r_reduced) # expressionNoteIR -- if binop <- [|+|, |-|] -- Expr_ok: p TC |- expression_l : typedExpressionIR_l -- Expr_ok: p TC |- expression_r : typedExpressionIR_r -- if (typedExpressionIR_l_cast, typedExpressionIR_r_cast) = $cast_binary(typedExpressionIR_l, typedExpressionIR_r) -- if (typedExpressionIR_l_reduced, typedExpressionIR_r_reduced) = $reduce_serenum_binary(typedExpressionIR_l_cast, typedExpressionIR_r_cast, $compat_satplusminus) -- if typeIR_reduced = $type_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if ctk_l_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if ctk_r_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_r_reduced) -- if ctk_reduced = $join_ctk(ctk_l_reduced, ctk_r_reduced) -- if expressionNoteIR = `(typeIR_reduced ctk_reduced)
14.6.1.3. Division and modulo
Click to view the specification source
rulegroup Expr_ok/binaryExpression-divmod: rule Expr_ok/rhs-lctk: p TC |- expression_l binop expression_r : (typedExpressionIR_l_reduced binop typedExpressionIR_r_reduced) # expressionNoteIR -- if binop <- [/, %] -- Expr_ok: p TC |- expression_l : typedExpressionIR_l -- Expr_ok: p TC |- expression_r : typedExpressionIR_r -- if (typedExpressionIR_l_cast, typedExpressionIR_r_cast) = $cast_binary(typedExpressionIR_l, typedExpressionIR_r) -- if (typedExpressionIR_l_reduced, typedExpressionIR_r_reduced) = $reduce_serenum_binary(typedExpressionIR_l_cast, typedExpressionIR_r_cast, $compat_divmod) -- if typeIR_reduced = $type_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if ctk_l_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if ctk_r_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_r_reduced) -- if ctk_r_reduced = LCTK -- Expr_eval_lctk: p TC |- typedExpressionIR_r_reduced ~> integerValue_r -- if n_r = $nat_of_integerValue(integerValue_r) -- if n_r > 0 -- if ctk_reduced = $join_ctk(ctk_l_reduced, ctk_r_reduced) -- if expressionNoteIR = `(typeIR_reduced ctk_reduced) rule Expr_ok/rhs-non-lctk: p TC |- expression_l binop expression_r : (typedExpressionIR_l_reduced binop typedExpressionIR_r_reduced) # expressionNoteIR -- if binop <- [/, %] -- Expr_ok: p TC |- expression_l : typedExpressionIR_l -- Expr_ok: p TC |- expression_r : typedExpressionIR_r -- if (typedExpressionIR_l_cast, typedExpressionIR_r_cast) = $cast_binary(typedExpressionIR_l, typedExpressionIR_r) -- if (typedExpressionIR_l_reduced, typedExpressionIR_r_reduced) = $reduce_serenum_binary(typedExpressionIR_l_cast, typedExpressionIR_r_cast, $compat_divmod) -- if typeIR_reduced = $type_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if ctk_l_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if ctk_r_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_r_reduced) -- if ctk_r_reduced =/= LCTK -- if ctk_reduced = $join_ctk(ctk_l_reduced, ctk_r_reduced) -- if expressionNoteIR = `(typeIR_reduced ctk_reduced)
14.6.1.4. Shift left and right
Click to view the specification source
rulegroup Expr_ok/binaryExpression-shift: rule Expr_ok/rhs-fixbit: p TC |- expression_l binop expression_r : (typedExpressionIR_l_reduced binop typedExpressionIR_r_reduced) # expressionNoteIR -- if binop <- [<<, >>] -- Expr_ok: p TC |- expression_l : typedExpressionIR_l -- Expr_ok: p TC |- expression_r : typedExpressionIR_r -- if (typedExpressionIR_l_reduced, typedExpressionIR_r_reduced) = $reduce_serenum_binary(typedExpressionIR_l, typedExpressionIR_r, $compat_shift) -- if typeIR_l_reduced = $type_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if ctk_l_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if typeIR_r_reduced = $type_of_typedExpressionIR(typedExpressionIR_r_reduced) -- if ctk_r_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_r_reduced) -- if BIT `<_> = typeIR_r_reduced -- if (typeIR_l_reduced = INT) => (ctk_r_reduced = LCTK) -- if ctk_reduced = $join_ctk(ctk_l_reduced, ctk_r_reduced) -- if expressionNoteIR = `(typeIR_l_reduced ctk_reduced) rule Expr_ok/rhs-non-fixbit: p TC |- expression_l binop expression_r : (typedExpressionIR_l_reduced binop typedExpressionIR_r_reduced) # expressionNoteIR -- if binop <- [<<, >>] -- Expr_ok: p TC |- expression_l : typedExpressionIR_l -- Expr_ok: p TC |- expression_r : typedExpressionIR_r -- if (typedExpressionIR_l_reduced, typedExpressionIR_r_reduced) = $reduce_serenum_binary(typedExpressionIR_l, typedExpressionIR_r, $compat_shift) -- if typeIR_l_reduced = $type_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if ctk_l_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if typeIR_r_reduced = $type_of_typedExpressionIR(typedExpressionIR_r_reduced) -- if ctk_r_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_r_reduced) -- if ~(typeIR_r_reduced <: fixedBitTypeIR) -- if ctk_r_reduced = LCTK -- if ctk_reduced = $join_ctk(ctk_l_reduced, ctk_r_reduced) -- if expressionNoteIR = `(typeIR_l_reduced ctk_reduced)
14.6.1.5. Equality and inequality
Click to view the specification source
rulegroup Expr_ok/binaryExpression-equality: rule Expr_ok/binaryExpression-equality: p TC |- expression_l binop expression_r : (typedExpressionIR_l_cast binop typedExpressionIR_r_cast) # expressionNoteIR -- if binop <- [==, !=] -- Expr_ok: p TC |- expression_l : typedExpressionIR_l -- Expr_ok: p TC |- expression_r : typedExpressionIR_r -- if (typedExpressionIR_l_cast, typedExpressionIR_r_cast) = $cast_binary(typedExpressionIR_l, typedExpressionIR_r) -- if typeIR_cast = $type_of_typedExpressionIR(typedExpressionIR_l_cast) -- if ctk_l_cast = $ctk_of_typedExpressionIR(typedExpressionIR_l_cast) -- if ctk_r_cast = $ctk_of_typedExpressionIR(typedExpressionIR_r_cast) -- if $is_equalable_typeIR(typeIR_cast) -- if ctk_cast = $join_ctk(ctk_l_cast, ctk_r_cast) -- if expressionNoteIR = `(BOOL ctk_cast)
14.6.1.6. Comparison
Click to view the specification source
rulegroup Expr_ok/binaryExpression-comparison: rule Expr_ok/binaryExpression-comparison: p TC |- expression_l binop expression_r : (typedExpressionIR_l_reduced binop typedExpressionIR_r_reduced) # expressionNoteIR -- if binop <- [<=, >=, <, >] -- Expr_ok: p TC |- expression_l : typedExpressionIR_l -- Expr_ok: p TC |- expression_r : typedExpressionIR_r -- if (typedExpressionIR_l_cast, typedExpressionIR_r_cast) = $cast_binary(typedExpressionIR_l, typedExpressionIR_r) -- if (typedExpressionIR_l_reduced, typedExpressionIR_r_reduced) = $reduce_serenum_binary(typedExpressionIR_l_cast, typedExpressionIR_r_cast, $compat_compare) -- if typeIR_reduced = $type_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if ctk_l_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if ctk_r_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_r_reduced) -- if ctk_reduced = $join_ctk(ctk_l_reduced, ctk_r_reduced) -- if expressionNoteIR = `(BOOL ctk_reduced)
14.6.1.7. Bitwise and, xor, and or
Click to view the specification source
rulegroup Expr_ok/binaryExpression-bitwise: rule Expr_ok/binaryExpression-bitwise: p TC |- expression_l binop expression_r : (typedExpressionIR_l_reduced binop typedExpressionIR_r_reduced) # expressionNoteIR -- if binop <- [&, ^, |] -- Expr_ok: p TC |- expression_l : typedExpressionIR_l -- Expr_ok: p TC |- expression_r : typedExpressionIR_r -- if (typedExpressionIR_l_cast, typedExpressionIR_r_cast) = $cast_binary(typedExpressionIR_l, typedExpressionIR_r) -- if (typedExpressionIR_l_reduced, typedExpressionIR_r_reduced) = $reduce_serenum_binary(typedExpressionIR_l_cast, typedExpressionIR_r_cast, $compat_bitwise) -- if typeIR_reduced = $type_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if ctk_l_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if ctk_r_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_r_reduced) -- if ctk_reduced = $join_ctk(ctk_l_reduced, ctk_r_reduced) -- if expressionNoteIR = `(typeIR_reduced ctk_reduced)
14.6.1.8. Concatenation
Click to view the specification source
rulegroup Expr_ok/binaryExpression-concat: rule Expr_ok/binaryExpression-concat: p TC |- expression_l ++ expression_r : (typedExpressionIR_l_reduced ++ typedExpressionIR_r_reduced) # expressionNoteIR -- Expr_ok: p TC |- expression_l : typedExpressionIR_l -- Expr_ok: p TC |- expression_r : typedExpressionIR_r -- if (typedExpressionIR_l_reduced, typedExpressionIR_r_reduced) = $reduce_serenum_binary(typedExpressionIR_l, typedExpressionIR_r, $compat_concat) -- if typeIR_l_reduced = $type_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if ctk_l_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if typeIR_r_reduced = $type_of_typedExpressionIR(typedExpressionIR_r_reduced) -- if ctk_r_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_r_reduced) -- if (typeIR_l_reduced = STRING /\ typeIR_r_reduced = STRING) => (ctk_l_reduced = LCTK /\ ctk_r_reduced = LCTK) -- if typeIR_reduced = $result_concat(typeIR_l_reduced, typeIR_r_reduced) -- if ctk_reduced = $join_ctk(ctk_l_reduced, ctk_r_reduced) -- if expressionNoteIR = `(typeIR_reduced ctk_reduced)
14.6.1.9. Logical and, and or
Click to view the specification source
rulegroup Expr_ok/binaryExpression-logical: rule Expr_ok/binaryExpression-logical: p TC |- expression_l binop expression_r : (typedExpressionIR_l_reduced binop typedExpressionIR_r_reduced) # expressionNoteIR -- if binop <- [&&, ||] -- Expr_ok: p TC |- expression_l : typedExpressionIR_l -- Expr_ok: p TC |- expression_r : typedExpressionIR_r -- if (typedExpressionIR_l_cast, typedExpressionIR_r_cast) = $cast_binary(typedExpressionIR_l, typedExpressionIR_r) -- if (typedExpressionIR_l_reduced, typedExpressionIR_r_reduced) = $reduce_serenum_binary(typedExpressionIR_l_cast, typedExpressionIR_r_cast, $compat_logical) -- if typeIR_reduced = $type_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if ctk_l_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if ctk_r_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_r_reduced) -- if ctk_reduced = $join_ctk(ctk_l_reduced, ctk_r_reduced) -- if expressionNoteIR = `(typeIR_reduced ctk_reduced)
14.6.2. Local compile-time evaluation
Click to view the specification source
rulegroup Expr_eval_lctk/binaryExpressionIR: rule Expr_eval_lctk/non-short-circuit: p TC |- (typedExpressionIR_l binop typedExpressionIR_r) # _ ~> $bin_op(binop, value_l, value_r) -- if ~(binop <- [&&, ||]) -- Expr_eval_lctk: p TC |- typedExpressionIR_l ~> value_l -- Expr_eval_lctk: p TC |- typedExpressionIR_r ~> value_r rule Expr_eval_lctk/land-false: p TC |- (typedExpressionIR_l && typedExpressionIR_r) # _ ~> (`B false) -- Expr_eval_lctk: p TC |- typedExpressionIR_l ~> (`B false) rule Expr_eval_lctk/land-true: p TC |- (typedExpressionIR_l && typedExpressionIR_r) # _ ~> value_r -- Expr_eval_lctk: p TC |- typedExpressionIR_l ~> (`B true) -- Expr_eval_lctk: p TC |- typedExpressionIR_r ~> value_r rule Expr_eval_lctk/lor-true: p TC |- (typedExpressionIR_l || typedExpressionIR_r) # _ ~> (`B true) -- Expr_eval_lctk: p TC |- typedExpressionIR_l ~> (`B true) rule Expr_eval_lctk/lor-false: p TC |- (typedExpressionIR_l || typedExpressionIR_r) # _ ~> value_r -- Expr_eval_lctk: p TC |- typedExpressionIR_l ~> (`B false) -- Expr_eval_lctk: p TC |- typedExpressionIR_r ~> value_r
14.6.3. Compile-time evaluation
Click to view the specification source
rulegroup Expr_inst/binaryExpressionIR: rule Expr_inst/non-short-circuit: p IC STO_0 |- (typedExpressionIR_l binop typedExpressionIR_r) # _ : STO_2 $bin_op(binop, value_l, value_r) -- if ~(binop <- [&&, ||]) -- Expr_inst: p IC STO_0 |- typedExpressionIR_l : STO_1 value_l -- Expr_inst: p IC STO_1 |- typedExpressionIR_r : STO_2 value_r rule Expr_inst/land-false: p IC STO_0 |- (typedExpressionIR_l && typedExpressionIR_r) # _ : STO_1 (`B false) -- Expr_inst: p IC STO_0 |- typedExpressionIR_l : STO_1 (`B false) rule Expr_inst/land-true: p IC STO_0 |- (typedExpressionIR_l && typedExpressionIR_r) # _ : STO_2 value_r -- Expr_inst: p IC STO_0 |- typedExpressionIR_l : STO_1 (`B true) -- Expr_inst: p IC STO_1 |- typedExpressionIR_r : STO_2 value_r rule Expr_inst/lor-true: p IC STO_0 |- (typedExpressionIR_l || typedExpressionIR_r) # _ : STO_1 (`B true) -- Expr_inst: p IC STO_0 |- typedExpressionIR_l : STO_1 (`B true) rule Expr_inst/lor-false: p IC STO_0 |- (typedExpressionIR_l || typedExpressionIR_r) # _ : STO_2 value_r -- Expr_inst: p IC STO_0 |- typedExpressionIR_l : STO_1 (`B false) -- Expr_inst: p IC STO_1 |- typedExpressionIR_r : STO_2 value_r
14.6.4. Runtime evaluation
Click to view the specification source
rulegroup Expr_eval/binaryExpressionIR: rule Expr_eval/non-short-circuit-abort: p EC_0 ARCH_0 |- (typedExpressionIR_l binop typedExpressionIR_r) # _ : EC_1 ARCH_1 abortResult -- if ~(binop <- [&&, ||]) -- Exprs_eval: p EC_0 ARCH_0 |- [typedExpressionIR_l, typedExpressionIR_r] : EC_1 ARCH_1 abortResult rule Expr_eval/non-short-circuit-cont: p EC_0 ARCH_0 |- (typedExpressionIR_l binop typedExpressionIR_r) # _ : EC_1 ARCH_1 expressionResult -- if ~(binop <- [&&, ||]) -- Exprs_eval: p EC_0 ARCH_0 |- [typedExpressionIR_l, typedExpressionIR_r] : EC_1 ARCH_1 (` ([value_l, value_r])) -- if expressionResult = ` $bin_op(binop, value_l, value_r) rule Expr_eval/land-abort: p EC_0 ARCH_0 |- (typedExpressionIR_l && typedExpressionIR_r) # _ : EC_1 ARCH_1 abortResult -- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_l : EC_1 ARCH_1 abortResult rule Expr_eval/land-false: p EC_0 ARCH_0 |- (typedExpressionIR_l && typedExpressionIR_r) # _ : EC_1 ARCH_1 expressionResult -- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_l : EC_1 ARCH_1 (` (`B false)) -- if expressionResult = ` (`B false) rule Expr_eval/land-true-abort: p EC_0 ARCH_0 |- (typedExpressionIR_l && typedExpressionIR_r) # _ : EC_2 ARCH_2 abortResult -- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_l : EC_1 ARCH_1 (` (`B true)) -- Expr_eval: p EC_1 ARCH_1 |- typedExpressionIR_r : EC_2 ARCH_2 abortResult rule Expr_eval/land-true: p EC_0 ARCH_0 |- (typedExpressionIR_l && typedExpressionIR_r) # _ : EC_2 ARCH_2 expressionResult_r -- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_l : EC_1 ARCH_1 (` (`B true)) -- Expr_eval: p EC_1 ARCH_1 |- typedExpressionIR_r : EC_2 ARCH_2 expressionResult_r rule Expr_eval/lor-abort: p EC_0 ARCH_0 |- (typedExpressionIR_l || typedExpressionIR_r) # _ : EC_1 ARCH_1 abortResult -- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_l : EC_1 ARCH_1 abortResult rule Expr_eval/lor-true: p EC_0 ARCH_0 |- (typedExpressionIR_l || typedExpressionIR_r) # _ : EC_1 ARCH_1 expressionResult -- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_l : EC_1 ARCH_1 (` (`B true)) -- if expressionResult = ` (`B true) rule Expr_eval/lor-false-abort: p EC_0 ARCH_0 |- (typedExpressionIR_l || typedExpressionIR_r) # _ : EC_2 ARCH_2 abortResult -- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_l : EC_1 ARCH_1 (` (`B false)) -- Expr_eval: p EC_1 ARCH_1 |- typedExpressionIR_r : EC_2 ARCH_2 abortResult rule Expr_eval/lor-false: p EC_0 ARCH_0 |- (typedExpressionIR_l || typedExpressionIR_r) # _ : EC_2 ARCH_2 expressionResult_r -- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_l : EC_1 ARCH_1 (` (`B false)) -- Expr_eval: p EC_1 ARCH_1 |- typedExpressionIR_r : EC_2 ARCH_2 expressionResult_r
14.7. Ternary expressions
A conditional expression of the form e1 ? e2 : e3 behaves the same as in
languages like C. As described above, the expression e1 is evaluated first,
and second either e2 or e3 is evaluated depending on the result.
The first sub-expression e1 must have Boolean type and the second and third
sub-expressions must have the same type, which cannot both be
arbitrary-precision integers unless the condition itself can be evaluated at
compilation time. This restriction is designed to ensure that the width of the
result of the conditional expression can be inferred statically at compile
time.
ternaryExpression
: expression ? expression : expression
;
14.7.1. Type checking
After type checking, ternary expressions have the form:
ternaryExpressionIR
: typedExpressionIR ? typedExpressionIR : typedExpressionIR
;
Click to view the specification source
rulegroup Expr_ok/ternaryExpression: rule Expr_ok/ternaryExpression: p TC |- expression_cond ? expression_true : expression_false : (typedExpressionIR_cond ? typedExpressionIR_true_cast : typedExpressionIR_false_cast) # expressionNoteIR -- Expr_ok: p TC |- expression_cond : typedExpressionIR_cond -- if typeIR_cond = $type_of_typedExpressionIR(typedExpressionIR_cond) -- if ctk_cond = $ctk_of_typedExpressionIR(typedExpressionIR_cond) -- if BOOL = $unroll_typeIR(typeIR_cond) -- Expr_ok: p TC |- expression_true : typedExpressionIR_true -- Expr_ok: p TC |- expression_false : typedExpressionIR_false -- if (typedExpressionIR_true_cast, typedExpressionIR_false_cast) = $cast_binary(typedExpressionIR_true, typedExpressionIR_false) -- if typeIR_cast = $type_of_typedExpressionIR(typedExpressionIR_true_cast) -- if ctk_true_cast = $ctk_of_typedExpressionIR(typedExpressionIR_true_cast) -- if ctk_false_cast = $ctk_of_typedExpressionIR(typedExpressionIR_false_cast) -- if (typeIR_cast = INT) => (ctk_cond =/= DYN) -- if ctk = $joins_ctk([ctk_cond, ctk_true_cast, ctk_false_cast]) -- if expressionNoteIR = `(typeIR_cast ctk)
14.7.2. Local compile-time evaluation
Click to view the specification source
rulegroup Expr_eval_lctk/ternaryExpressionIR: rule Expr_eval_lctk/true: p TC |- (typedExpressionIR_cond ? typedExpressionIR_true : typedExpressionIR_false) # _ ~> value_true -- Expr_eval_lctk: p TC |- typedExpressionIR_cond ~> (`B true) -- Expr_eval_lctk: p TC |- typedExpressionIR_true ~> value_true rule Expr_eval_lctk/false: p TC |- (typedExpressionIR_cond ? typedExpressionIR_true : typedExpressionIR_false) # _ ~> value_false -- Expr_eval_lctk: p TC |- typedExpressionIR_cond ~> (`B false) -- Expr_eval_lctk: p TC |- typedExpressionIR_false ~> value_false
14.7.3. Compile-time evaluation
Click to view the specification source
rulegroup Expr_inst/ternaryExpressionIR: rule Expr_inst/true: p IC STO_0 |- (typedExpressionIR_cond ? typedExpressionIR_true : typedExpressionIR_false) # _ : STO_2 value_true -- Expr_inst: p IC STO_0 |- typedExpressionIR_cond : STO_1 (`B true) -- Expr_inst: p IC STO_1 |- typedExpressionIR_true : STO_2 value_true rule Expr_inst/false: p IC STO_0 |- (typedExpressionIR_cond ? typedExpressionIR_true : typedExpressionIR_false) # _ : STO_2 value_false -- Expr_inst: p IC STO_0 |- typedExpressionIR_cond : STO_1 (`B false) -- Expr_inst: p IC STO_1 |- typedExpressionIR_false : STO_2 value_false
14.7.4. Runtime evaluation
Click to view the specification source
rulegroup Expr_eval/ternaryExpressionIR: rule Expr_eval/cond-abort: p EC_0 ARCH_0 |- (typedExpressionIR_cond ? typedExpressionIR_true : typedExpressionIR_false) # _ : EC_1 ARCH_1 abortResult -- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_cond : EC_1 ARCH_1 abortResult rule Expr_eval/cond-true: p EC_0 ARCH_0 |- (typedExpressionIR_cond ? typedExpressionIR_true : typedExpressionIR_false) # _ : EC_2 ARCH_2 expressionResult_true -- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_cond : EC_1 ARCH_1 (` (`B true)) -- Expr_eval: p EC_1 ARCH_1 |- typedExpressionIR_true : EC_2 ARCH_2 expressionResult_true rule Expr_eval/cond-false: p EC_0 ARCH_0 |- (typedExpressionIR_cond ? typedExpressionIR_true : typedExpressionIR_false) # _ : EC_2 ARCH_2 expressionResult_false -- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_cond : EC_1 ARCH_1 (` (`B false)) -- Expr_eval: p EC_1 ARCH_1 |- typedExpressionIR_false : EC_2 ARCH_2 expressionResult_false
14.8. Cast expressions
P4 provides a limited set of casts between types. A cast is written (t) e,
where t is a type and e is an expression.
castExpression
: `( type ) expression
;
14.8.1. Type checking
Click to view the specification source
rulegroup Expr_ok/castExpression: rule Expr_ok/castExpression: p TC |- `(type_t) expression : (`(typeIR_t) typedExpressionIR) # expressionNoteIR -- Type_ok: p TC |- type_t : typeIR_t # eps -- Type_wf: $bound(p, TC) |- typeIR_t -- Expr_ok: p TC |- expression : typedExpressionIR -- if typeIR = $type_of_typedExpressionIR(typedExpressionIR) -- if ctk = $ctk_of_typedExpressionIR(typedExpressionIR) -- Cast_expl: typeIR -> typeIR_t -- if expressionNoteIR = `(typeIR_t ctk)
14.8.2. Local compile-time evaluation
Click to view the specification source
rulegroup Expr_eval_lctk/castExpressionIR: rule Expr_eval_lctk/castExpressionIR: p TC |- (`(typeIR) typedExpressionIR) # _ ~> value_cast -- Expr_eval_lctk: p TC |- typedExpressionIR ~> value -- if value_cast = $cast_op(typeIR, value)
14.8.3. Compile-time evaluation
Click to view the specification source
rulegroup Expr_inst/castExpressionIR: rule Expr_inst/castExpressionIR: p IC STO_0 |- (`(typeIR) typedExpressionIR) # _ : STO_1 value_cast -- Expr_inst: p IC STO_0 |- typedExpressionIR : STO_1 value -- if value_cast = $cast_op(typeIR, value)
14.8.4. Runtime evaluation
Click to view the specification source
rulegroup Expr_eval/castExpressionIR: rule Expr_eval/abort: p EC_0 ARCH_0 |- (`(typeIR) typedExpressionIR) # _ : EC_1 ARCH_1 abortResult -- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 abortResult rule Expr_eval/cont: p EC_0 ARCH_0 |- (`(typeIR) typedExpressionIR) # _ : EC_1 ARCH_1 (` value_cast) -- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 (` value) -- if value_cast = $cast_op(typeIR, value)
14.9. Invalid header expressions
{#} represents an invalid header expression. See Section 8.6.2 for
more details.
invalidHeaderExpression
: {#}
;
14.9.1. Type checking
After type checking, an invalid header expression is represented as:
invalidHeaderExpressionIR = invalidHeaderExpression
Click to view the specification source
rulegroup Expr_ok/invalidHeaderExpression:
rule Expr_ok/invalidHeaderExpression:
p TC |- {#} : {#} # expressionNoteIR
-- if expressionNoteIR = `(HEADER_INVALID LCTK)
14.9.2. Local compile-time evaluation
Click to view the specification source
rulegroup Expr_eval_lctk/invalidHeaderExpressionIR:
rule Expr_eval_lctk/invalidHeaderExpressionIR:
p TC |- {#} # _ ~> {#}
14.9.3. Compile-time evaluation
Click to view the specification source
rulegroup Expr_inst/invalidHeaderExpressionIR:
rule Expr_inst/invalidHeaderExpressionIR:
p IC STO |- {#} # _ : STO {#}
14.9.4. Runtime evaluation
Click to view the specification source
rulegroup Expr_eval/invalidHeaderExpressionIR:
rule Expr_eval/invalidHeaderExpressionIR:
p EC ARCH |- {#} # _ : EC ARCH expressionResult
-- if expressionResult = ` {#}
14.10. Sequence expressions
A sequence expression is a comma-separated list of expressions enclosed in
curly braces {}. See Section 8.6.3 for details.
sequenceOrRecordExpression
: `{ sequenceOrRecordElementExpression trailingCommaOpt }
;
sequenceElementExpression = expressionList
sequenceOrRecordElementExpression
: sequenceElementExpression
| recordElementExpression
;
14.10.1. Type checking
After type checking, a sequence expression is represented as:
sequenceExpressionIR
: SEQ `{ typedExpressionListIR }
| SEQ `{ typedExpressionListIR , ... }
;
Click to view the specification source
rulegroup Expr_ok/sequenceExpression:
rule Expr_ok/non-default:
p TC |- `{expressionList _} : (SEQ `{typedExpressionIR_e*}) # expressionNoteIR
-- if expression_e* = $flatten_expressionList(expressionList)
-- if ~(... <- expression_e*)
-- (Expr_ok: p TC |- expression_e : typedExpressionIR_e)*
-- (if (typeIR_e = $type_of_typedExpressionIR(typedExpressionIR_e)))*
-- (if (ctk_e = $ctk_of_typedExpressionIR(typedExpressionIR_e)))*
-- if typeIR = SEQ `<typeIR_e*>
-- if ctk = $joins_ctk(ctk_e*)
-- if expressionNoteIR = `(typeIR ctk)
rule Expr_ok/default:
p TC |- `{expressionList _} : (SEQ `{typedExpressionIR_e_h* , ...}) # expressionNoteIR
-- if expression_e* = $flatten_expressionList(expressionList)
-- if ... <- expression_e*
-- if ... :: expression_e_h_rev* = $rev_<expression>(expression_e*)
-- if expression_e_h* = $rev_<expression>(expression_e_h_rev*)
-- if ~(... <- expression_e_h*)
-- (Expr_ok: p TC |- expression_e_h : typedExpressionIR_e_h)*
-- (if (typeIR_e_h = $type_of_typedExpressionIR(typedExpressionIR_e_h)))*
-- (if (ctk_e_h = $ctk_of_typedExpressionIR(typedExpressionIR_e_h)))*
-- if typeIR = SEQ `<typeIR_e_h* , ...>
-- if ctk = $joins_ctk(ctk_e_h*)
-- if expressionNoteIR = `(typeIR ctk)
14.10.2. Local compile-time evaluation
Click to view the specification source
rulegroup Expr_eval_lctk/sequenceExpressionIR:
rule Expr_eval_lctk/non-default:
p TC |- (SEQ `{typedExpressionIR*}) # _ ~> SEQ `(value*)
-- (Expr_eval_lctk: p TC |- typedExpressionIR ~> value)*
rule Expr_eval_lctk/default:
p TC |- (SEQ `{typedExpressionIR* , ...}) # _ ~> SEQ `(value* , ...)
-- (Expr_eval_lctk: p TC |- typedExpressionIR ~> value)*
14.10.3. Compile-time evaluation
Click to view the specification source
rulegroup Expr_inst/sequenceExpressionIR:
rule Expr_inst/non-default:
p IC STO_0 |- (SEQ `{typedExpressionListIR}) # _ : STO_1 (SEQ `(value*))
-- Exprs_inst: p IC STO_0 |- typedExpressionListIR : STO_1 value*
rule Expr_inst/default:
p IC STO_0 |- (SEQ `{typedExpressionListIR , ...}) # _ : STO_1 (SEQ `(value* , ...))
-- Exprs_inst: p IC STO_0 |- typedExpressionListIR : STO_1 value*
14.10.4. Runtime evaluation
Click to view the specification source
rulegroup Expr_eval/sequenceExpressionIR:
rule Expr_eval/non-default-abort:
p EC_0 ARCH_0 |- (SEQ `{typedExpressionListIR}) # _ : EC_1 ARCH_1 abortResult
-- Exprs_eval: p EC_0 ARCH_0 |- typedExpressionListIR : EC_1 ARCH_1 abortResult
rule Expr_eval/non-default-cont:
p EC_0 ARCH_0 |- (SEQ `{typedExpressionListIR}) # _ : EC_1 ARCH_1 expressionResult
-- Exprs_eval: p EC_0 ARCH_0 |- typedExpressionListIR : EC_1 ARCH_1 (` value*)
-- if expressionResult = ` (SEQ `(value*))
rule Expr_eval/default-abort:
p EC_0 ARCH_0 |- (SEQ `{typedExpressionListIR , ...}) # _ : EC_1 ARCH_1 abortResult
-- Exprs_eval: p EC_0 ARCH_0 |- typedExpressionListIR : EC_1 ARCH_1 abortResult
rule Expr_eval/default-cont:
p EC_0 ARCH_0 |- (SEQ `{typedExpressionListIR , ...}) # _ : EC_1 ARCH_1 expressionResult
-- Exprs_eval: p EC_0 ARCH_0 |- typedExpressionListIR : EC_1 ARCH_1 (` value*)
-- if expressionResult = ` (SEQ `(value* , ...))
14.11. Record expressions
A record expression is a comma-separated list of named expressions enclosed in
curly braces {}. See Section 8.6.4 for details.
sequenceOrRecordExpression
: `{ sequenceOrRecordElementExpression trailingCommaOpt }
;
recordElementExpression
: name = expression
| name = expression , ...
| name = expression , namedExpressionList
| name = expression , namedExpressionList , ...
;
sequenceOrRecordElementExpression
: sequenceElementExpression
| recordElementExpression
;
14.11.1. Type checking
After type checking, a record expression is represented as:
recordExpressionIR
: RECORD `{ namedExpressionListIR }
| RECORD `{ namedExpressionListIR , ... }
;
Click to view the specification source
rulegroup Expr_ok/recordExpression:
rule Expr_ok/single-non-default:
p TC |- `{(name_f = expression_f) _} : (RECORD `{(nameIR_f = typedExpressionIR_f)}) # expressionNoteIR
-- if nameIR_f = $name(name_f)
-- Expr_ok: p TC |- expression_f : typedExpressionIR_f
-- if typeIR_f = $type_of_typedExpressionIR(typedExpressionIR_f)
-- if ctk_f = $ctk_of_typedExpressionIR(typedExpressionIR_f)
-- if typeIR = RECORD `{(`EMPTY typeIR_f nameIR_f ;)}
-- if expressionNoteIR = `(typeIR ctk_f)
rule Expr_ok/single-default:
p TC |- `{(name_f = expression_f , ...) _} : (RECORD `{(nameIR_f = typedExpressionIR_f) , ...}) # expressionNoteIR
-- if nameIR_f = $name(name_f)
-- Expr_ok: p TC |- expression_f : typedExpressionIR_f
-- if typeIR_f = $type_of_typedExpressionIR(typedExpressionIR_f)
-- if ctk_f = $ctk_of_typedExpressionIR(typedExpressionIR_f)
-- if typeIR = RECORD `{(`EMPTY typeIR_f nameIR_f ;) , ...}
-- if expressionNoteIR = `(typeIR ctk_f)
rule Expr_ok/multiple-non-default:
p TC |- `{(name_f_h = expression_f_h , namedExpressionList_t) _} : (RECORD `{(nameIR_f = typedExpressionIR_f)*}) # expressionNoteIR
-- if (name_f_t = expression_f_t)* = $flatten_namedExpressionList(namedExpressionList_t)
-- if name_f* = name_f_h :: name_f_t*
-- (if (nameIR_f = $name(name_f)))*
-- if expression_f* = expression_f_h :: expression_f_t*
-- (Expr_ok: p TC |- expression_f : typedExpressionIR_f)*
-- (if (typeIR_f = $type_of_typedExpressionIR(typedExpressionIR_f)))*
-- (if (ctk_f = $ctk_of_typedExpressionIR(typedExpressionIR_f)))*
-- if typeIR = RECORD `{(`EMPTY typeIR_f nameIR_f ;)*}
-- if ctk = $joins_ctk(ctk_f*)
-- if expressionNoteIR = `(typeIR ctk)
rule Expr_ok/multiple-default:
p TC |- `{(name_f_h = expression_f_h , namedExpressionList_t , ...) _} : (RECORD `{(nameIR_f = typedExpressionIR_f)* , ...}) # expressionNoteIR
-- if (name_f_t = expression_f_t)* = $flatten_namedExpressionList(namedExpressionList_t)
-- if name_f* = name_f_h :: name_f_t*
-- (if (nameIR_f = $name(name_f)))*
-- if expression_f* = expression_f_h :: expression_f_t*
-- (Expr_ok: p TC |- expression_f : typedExpressionIR_f)*
-- (if (typeIR_f = $type_of_typedExpressionIR(typedExpressionIR_f)))*
-- (if (ctk_f = $ctk_of_typedExpressionIR(typedExpressionIR_f)))*
-- if typeIR = RECORD `{(`EMPTY typeIR_f nameIR_f ;)* , ...}
-- if ctk = $joins_ctk(ctk_f*)
-- if expressionNoteIR = `(typeIR ctk)
14.11.2. Local compile-time evaluation
Click to view the specification source
rulegroup Expr_eval_lctk/recordExpressionIR:
rule Expr_eval_lctk/non-default:
p TC |- (RECORD `{(nameIR = typedExpressionIR)*}) # _ ~> RECORD `{(value nameIR ;)*}
-- (Expr_eval_lctk: p TC |- typedExpressionIR ~> value)*
rule Expr_eval_lctk/default:
p TC |- (RECORD `{(nameIR = typedExpressionIR)* , ...}) # _ ~> RECORD `{(value nameIR ;)* , ...}
-- (Expr_eval_lctk: p TC |- typedExpressionIR ~> value)*
14.11.3. Compile-time evaluation
Click to view the specification source
rulegroup Expr_inst/recordExpressionIR:
rule Expr_inst/non-default:
p IC STO_0 |- (RECORD `{(nameIR = typedExpressionIR)*}) # _ : STO_1 (RECORD `{(value nameIR ;)*})
-- Exprs_inst: p IC STO_0 |- typedExpressionIR* : STO_1 value*
rule Expr_inst/default:
p IC STO_0 |- (RECORD `{(nameIR = typedExpressionIR)* , ...}) # _ : STO_1 (RECORD `{(value nameIR ;)* , ...})
-- Exprs_inst: p IC STO_0 |- typedExpressionIR* : STO_1 value*
14.11.4. Runtime evaluation
Click to view the specification source
rulegroup Expr_eval/recordExpressionIR:
rule Expr_eval/non-default-abort:
p EC_0 ARCH_0 |- (RECORD `{(nameIR = typedExpressionIR)*}) # _ : EC_1 ARCH_1 abortResult
-- Exprs_eval: p EC_0 ARCH_0 |- typedExpressionIR* : EC_1 ARCH_1 abortResult
rule Expr_eval/non-default-cont:
p EC_0 ARCH_0 |- (RECORD `{(nameIR = typedExpressionIR)*}) # _ : EC_1 ARCH_1 expressionResult
-- Exprs_eval: p EC_0 ARCH_0 |- typedExpressionIR* : EC_1 ARCH_1 (` value*)
-- if expressionResult = ` (RECORD `{(value nameIR ;)*})
rule Expr_eval/default-abort:
p EC_0 ARCH_0 |- (RECORD `{(nameIR = typedExpressionIR)* , ...}) # _ : EC_1 ARCH_1 abortResult
-- Exprs_eval: p EC_0 ARCH_0 |- typedExpressionIR* : EC_1 ARCH_1 abortResult
rule Expr_eval/default-cont:
p EC_0 ARCH_0 |- (RECORD `{(nameIR = typedExpressionIR)* , ...}) # _ : EC_1 ARCH_1 expressionResult
-- Exprs_eval: p EC_0 ARCH_0 |- typedExpressionIR* : EC_1 ARCH_1 (` value*)
-- if expressionResult = ` (RECORD `{(value nameIR ;)* , ...})
14.12. Error access expression
An error access expression accesses a symbolic name in the error namespace.
See Section 8.2.4 for details about error types and values.
errorAccessExpression
: ERROR . member
;
14.12.1. Type checking
After type checking, an error access expression is represented as:
errorAccessExpressionIR
: ERROR . nameIR
;
Click to view the specification source
rulegroup Expr_ok/errorAccessExpression: rule Expr_ok/errorAccessExpression: p TC |- ERROR . member : (ERROR . nameIR) # expressionNoteIR -- if nameIR = $name(member) -- if nameIR_error = "error." ++ nameIR -- if ERROR . nameIR = $find_var_value_t(` nameIR_error, p, TC) -- if expressionNoteIR = `(ERROR LCTK)
14.12.2. Local compile-time evaluation
Click to view the specification source
rulegroup Expr_eval_lctk/errorAccessExpressionIR: rule Expr_eval_lctk/errorAccessExpressionIR: p TC |- (ERROR . nameIR) # `(_ _) ~> value_error -- if nameIR_error = "error." ++ nameIR -- if value_error = $find_var_value_t(` nameIR_error, p, TC)
14.12.3. Compile-time evaluation
Click to view the specification source
rulegroup Expr_inst/errorAccessExpressionIR: rule Expr_inst/errorAccessExpressionIR: p IC STO |- (ERROR . nameIR) # _ : STO value_error -- if nameIR_error = "error." ++ nameIR -- if value_error = $find_var_i(` nameIR_error, p, IC)
14.12.4. Runtime evaluation
Click to view the specification source
rulegroup Expr_eval/errorAccessExpressionIR: rule Expr_eval/errorAccessExpressionIR: p EC ARCH |- (ERROR . nameIR) # _ : EC ARCH expressionResult -- if nameIR_error = "error." ++ nameIR -- if value_error = $find_var_e(` nameIR_error, p, EC) -- if expressionResult = ` value_error
14.13. Member access expressions
A member access expression accesses a user-defined or built-in field in an aggregate value. Also, it may access an element defined in an enum declaration.
memberAccessExpression
: memberAccessBase . member
;
memberAccessBase
: prefixedTypeName
| expression
;
14.13.1. Type checking
After type checking, a member access expression is represented in P4IR as:
memberAccessExpressionIR
: memberAccessBaseIR . nameIR
;
memberAccessBaseIR
: TYPE prefixedNameIR
| typedExpressionIR
;
Accessing an enum member
Click to view the specification source
rulegroup Expr_ok/memberAccessExpression-type:
rule Expr_ok/enum:
p TC |- prefixedTypeName_base . member : ((TYPE prefixedNameIR_base) . nameIR) # expressionNoteIR
-- if prefixedNameIR_base = $prefixedTypeName(prefixedTypeName_base)
-- if typeDefIR_base = $find_typeDef_t(p, TC, prefixedNameIR_base)
-- if $is_monomorphic_typeDefIR(typeDefIR_base)
-- if typeIR_base = $typeIR_of_typeDefIR(typeDefIR_base)
-- if ENUM _ `{nameIR_field*} = $unroll_typeIR(typeIR_base)
-- if nameIR = $name(member)
-- if nameIR <- nameIR_field*
-- if expressionNoteIR = `(typeIR_base LCTK)
rule Expr_ok/serenum:
p TC |- prefixedTypeName_base . member : ((TYPE prefixedNameIR_base) . nameIR) # expressionNoteIR
-- if prefixedNameIR_base = $prefixedTypeName(prefixedTypeName_base)
-- if typeDefIR_base = $find_typeDef_t(p, TC, prefixedNameIR_base)
-- if $is_monomorphic_typeDefIR(typeDefIR_base)
-- if typeIR_base = $typeIR_of_typeDefIR(typeDefIR_base)
-- if ENUM _ `<_> `{(nameIR_field = _ ;)*} = $unroll_typeIR(typeIR_base)
-- if nameIR = $name(member)
-- if nameIR <- nameIR_field*
-- if expressionNoteIR = `(typeIR_base LCTK)
Accessing an aggregate member
Click to view the specification source
rulegroup Expr_ok/memberAccessExpression-expression:
rule Expr_ok/headerStack-size:
p TC |- expression_base . member : (typedExpressionIR_base . "size") # expressionNoteIR
-- Expr_ok: p TC |- expression_base : typedExpressionIR_base
-- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base)
-- if typeIR `[n_size] = $unroll_typeIR(typeIR_base)
-- if "size" = $name(member)
-- if expressionNoteIR = `((BIT `<32>) LCTK)
rule Expr_ok/headerStack-lastIndex:
p TC |- expression_base . member : (typedExpressionIR_base . "lastIndex") # expressionNoteIR
-- Expr_ok: p TC |- expression_base : typedExpressionIR_base
-- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base)
-- if typeIR `[n_size] = $unroll_typeIR(typeIR_base)
-- if "lastIndex" = $name(member)
-- if (p = BLOCK /\ TC.BLOCK.KIND = PARSER) \/ (p = LOCAL /\ TC.LOCAL.KIND = PARSER_STATE)
-- if expressionNoteIR = `((BIT `<32>) DYN)
rule Expr_ok/headerStack-last:
p TC |- expression_base . member : (typedExpressionIR_base . "last") # expressionNoteIR
-- Expr_ok: p TC |- expression_base : typedExpressionIR_base
-- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base)
-- if typeIR `[n_size] = $unroll_typeIR(typeIR_base)
-- if "last" = $name(member)
-- if (p = BLOCK /\ TC.BLOCK.KIND = PARSER) \/ (p = LOCAL /\ TC.LOCAL.KIND = PARSER_STATE)
-- if expressionNoteIR = `(typeIR DYN)
rule Expr_ok/headerStack-next:
p TC |- expression_base . member : (typedExpressionIR_base . "next") # expressionNoteIR
-- Expr_ok: p TC |- expression_base : typedExpressionIR_base
-- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base)
-- if typeIR `[n_size] = $unroll_typeIR(typeIR_base)
-- if "next" = $name(member)
-- if (p = BLOCK /\ TC.BLOCK.KIND = PARSER) \/ (p = LOCAL /\ TC.LOCAL.KIND = PARSER_STATE)
-- if expressionNoteIR = `(typeIR DYN)
rule Expr_ok/struct:
p TC |- expression_base . member : (typedExpressionIR_base . nameIR) # expressionNoteIR
-- Expr_ok: p TC |- expression_base : typedExpressionIR_base
-- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base)
-- if ctk_base = $ctk_of_typedExpressionIR(typedExpressionIR_base)
-- if STRUCT _ `<_> `{(_ typeIR_f nameIR_f ;)*} = $unroll_typeIR(typeIR_base)
-- if nameIR = $name(member)
-- if typeIR = $assoc_<nameIR, typeIR>(nameIR, (nameIR_f, typeIR_f)*)
-- if expressionNoteIR = `(typeIR ctk_base)
rule Expr_ok/header:
p TC |- expression_base . member : (typedExpressionIR_base . nameIR) # expressionNoteIR
-- Expr_ok: p TC |- expression_base : typedExpressionIR_base
-- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base)
-- if ctk_base = $ctk_of_typedExpressionIR(typedExpressionIR_base)
-- if HEADER _ `<_> `{(_ typeIR_f nameIR_f ;)*} = $unroll_typeIR(typeIR_base)
-- if nameIR = $name(member)
-- if typeIR = $assoc_<nameIR, typeIR>(nameIR, (nameIR_f, typeIR_f)*)
-- if expressionNoteIR = `(typeIR ctk_base)
rule Expr_ok/header-union:
p TC |- expression_base . member : (typedExpressionIR_base . nameIR) # expressionNoteIR
-- Expr_ok: p TC |- expression_base : typedExpressionIR_base
-- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base)
-- if ctk_base = $ctk_of_typedExpressionIR(typedExpressionIR_base)
-- if HEADER_UNION _ `<_> `{(_ typeIR_f nameIR_f ;)*} = $unroll_typeIR(typeIR_base)
-- if nameIR = $name(member)
-- if typeIR = $assoc_<nameIR, typeIR>(nameIR, (nameIR_f, typeIR_f)*)
-- if expressionNoteIR = `(typeIR ctk_base)
rule Expr_ok/table-struct-hit:
p TC |- expression_base . member : (typedExpressionIR_base . nameIR) # expressionNoteIR
-- Expr_ok: p TC |- expression_base : typedExpressionIR_base
-- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base)
-- if TABLE_STRUCT _ `{HIT boolTypeIR ; MISS _ ; ACTION_RUN _ ;} = $unroll_typeIR(typeIR_base)
-- if nameIR = $name(member)
-- if nameIR = "hit"
-- if expressionNoteIR = `(boolTypeIR DYN)
rule Expr_ok/table-struct-miss:
p TC |- expression_base . member : (typedExpressionIR_base . nameIR) # expressionNoteIR
-- Expr_ok: p TC |- expression_base : typedExpressionIR_base
-- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base)
-- if TABLE_STRUCT _ `{HIT _ ; MISS boolTypeIR ; ACTION_RUN _ ;} = $unroll_typeIR(typeIR_base)
-- if nameIR = $name(member)
-- if nameIR = "miss"
-- if expressionNoteIR = `(boolTypeIR DYN)
rule Expr_ok/table-struct-action_run:
p TC |- expression_base . member : (typedExpressionIR_base . nameIR) # expressionNoteIR
-- Expr_ok: p TC |- expression_base : typedExpressionIR_base
-- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base)
-- if TABLE_STRUCT _ `{HIT _ ; MISS _ ; ACTION_RUN tableMetadataEnumTypeIR ;} = $unroll_typeIR(typeIR_base)
-- if nameIR = $name(member)
-- if nameIR = "action_run"
-- if expressionNoteIR = `(tableMetadataEnumTypeIR DYN)
14.13.2. Local compile-time evaluation
Accessing an enum member
Click to view the specification source
rulegroup Expr_eval_lctk/memberAccessExpressionIR-type:
rule Expr_eval_lctk/enum:
p TC |- ((TYPE prefixedNameIR_base) . nameIR) # _ ~> typeId . nameIR
-- if typeDefIR_base = $find_typeDef_t(p, TC, prefixedNameIR_base)
-- if $is_monomorphic_typeDefIR(typeDefIR_base)
-- if typeIR_base = $typeIR_of_typeDefIR(typeDefIR_base)
-- if ENUM typeId `{nameIR_field*} = $unroll_typeIR(typeIR_base)
-- if nameIR <- nameIR_field*
rule Expr_eval_lctk/serenum:
p TC |- ((TYPE prefixedNameIR_base) . nameIR) # _ ~> typeId . nameIR . value
-- if typeDefIR_base = $find_typeDef_t(p, TC, prefixedNameIR_base)
-- if $is_monomorphic_typeDefIR(typeDefIR_base)
-- if typeIR_base = $typeIR_of_typeDefIR(typeDefIR_base)
-- if ENUM typeId `<typeIR> `{(nameIR_field = value_field ;)*} = $unroll_typeIR(typeIR_base)
-- if value = $assoc_<nameIR, value>(nameIR, (nameIR_field, value_field)*)
Accessing an aggregate member
Click to view the specification source
rulegroup Expr_eval_lctk/memberAccessExpressionIR-typedExpressionIR:
rule Expr_eval_lctk/header-stack-size:
p TC |- (typedExpressionIR_base . nameIR) # _ ~> D n_size
-- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base)
-- if _ `[n_size] = $unroll_typeIR(typeIR_base)
-- if nameIR = "size"
rule Expr_eval_lctk/struct:
p TC |- (typedExpressionIR_base . nameIR) # _ ~> value
-- Expr_eval_lctk: p TC |- typedExpressionIR_base ~> structValue
-- if STRUCT _ `{(value_field nameIR_field ;)*} = structValue
-- if value = $assoc_<nameIR, value>(nameIR, (nameIR_field, value_field)*)
rule Expr_eval_lctk/header:
p TC |- (typedExpressionIR_base . nameIR) # _ ~> value
-- Expr_eval_lctk: p TC |- typedExpressionIR_base ~> headerValue
-- if HEADER _ `{_ ; (value_field nameIR_field ;)*} = headerValue
-- if value = $assoc_<nameIR, value>(nameIR, (nameIR_field, value_field)*)
rule Expr_eval_lctk/header-union:
p TC |- (typedExpressionIR_base . nameIR) # _ ~> value
-- Expr_eval_lctk: p TC |- typedExpressionIR_base ~> headerUnionValue
-- if HEADER_UNION _ `{(value_field nameIR_field ;)*} = headerUnionValue
-- if value = $assoc_<nameIR, value>(nameIR, (nameIR_field, value_field)*)
14.13.3. Compile-time evaluation
Accessing an enum member
Click to view the specification source
rulegroup Expr_inst/memberAccessExpressionIR-type:
rule Expr_inst/enum:
p IC STO |- ((TYPE prefixedNameIR) . nameIR) # _ : STO (typeId . nameIR)
-- if ENUM typeId `{_} = $find_typeDef_i(p, IC, prefixedNameIR)
rule Expr_inst/serenum:
p IC STO |- ((TYPE prefixedNameIR) . nameIR) # _ : STO (typeId . nameIR . value)
-- if ENUM typeId `<_> `{(id_member = value_member ;)*} = $find_typeDef_i(p, IC, prefixedNameIR)
-- if value = $assoc_<nameIR, value>(nameIR, (id_member, value_member)*)
Accessing an aggregate member
Click to view the specification source
rulegroup Expr_inst/memberAccessExpressionIR-typedExpressionIR:
rule Expr_inst/header-stack-size:
p IC STO |- (typedExpressionIR_base . nameIR) # _ : STO (D n_size)
-- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base)
-- if _ `[n_size] = $unroll_typeIR(typeIR_base)
-- if nameIR = "size"
rule Expr_inst/struct:
p IC STO_0 |- (typedExpressionIR_base . nameIR) # _ : STO_1 value
-- Expr_inst: p IC STO_0 |- typedExpressionIR_base : STO_1 structValue
-- if STRUCT _ `{(value_field id_field ;)*} = structValue
-- if value = $assoc_<id, value>(nameIR, (id_field, value_field)*)
rule Expr_inst/header:
p IC STO_0 |- (typedExpressionIR_base . nameIR) # _ : STO_1 value
-- Expr_inst: p IC STO_0 |- typedExpressionIR_base : STO_1 headerValue
-- if HEADER _ `{_ ; (value_field id_field ;)*} = headerValue
-- if value = $assoc_<id, value>(nameIR, (id_field, value_field)*)
rule Expr_inst/header-union:
p IC STO_0 |- (typedExpressionIR_base . nameIR) # _ : STO_1 value
-- Expr_inst: p IC STO_0 |- typedExpressionIR_base : STO_1 headerUnionValue
-- if HEADER_UNION _ `{(value_field id_field ;)*} = headerUnionValue
-- if value = $assoc_<id, value>(nameIR, (id_field, value_field)*)
14.13.4. Runtime evaluation
Accessing an enum member
Click to view the specification source
rulegroup Expr_eval/memberAccessExpressionIR-type:
rule Expr_eval/enum:
p EC ARCH |- ((TYPE prefixedNameIR) . nameIR) # _ : EC ARCH expressionResult
-- if ENUM typeId `{_} = $find_typeDef_e(p, EC, prefixedNameIR)
-- if expressionResult = ` (typeId . nameIR)
rule Expr_eval/serenum:
p EC ARCH |- ((TYPE prefixedNameIR) . nameIR) # _ : EC ARCH expressionResult
-- if ENUM typeId `<_> `{(nameIR_member = value_member ;)*} = $find_typeDef_e(p, EC, prefixedNameIR)
-- if value = $assoc_<nameIR, value>(nameIR, (nameIR_member, value_member)*)
-- if expressionResult = ` (typeId . nameIR . value)
Accessing an aggregate member
Click to view the specification source
rulegroup Expr_eval/memberAccessExpressionIR-typedExpressionIR:
rule Expr_eval/abort:
p EC_0 ARCH_0 |- (typedExpressionIR_base . nameIR) # `(typeIR_base _) : EC_1 ARCH_1 abortResult
-- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_base : EC_1 ARCH_1 abortResult
rule Expr_eval/stack-size:
p EC_0 ARCH_0 |- (typedExpressionIR_base . nameIR) # `(typeIR_base _) : EC_1 ARCH_1 expressionResult
-- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_base : EC_1 ARCH_1 (` headerStackValue_base)
-- if HEADER_STACK `[_ `(_ ; n_size)] = headerStackValue_base
-- if nameIR = "size"
-- if expressionResult = ` (D n_size)
rule Expr_eval/stack-last-out-of-bounds:
p EC_0 ARCH_0 |- (typedExpressionIR_base . nameIR) # `(typeIR_base _) : EC_1 ARCH_1 rejectTransitionResult
-- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_base : EC_1 ARCH_1 (` headerStackValue_base)
-- if HEADER_STACK `[value_elem* `(n_idx ; n_size)] = headerStackValue_base
-- if nameIR = "last"
-- if b_out_of_bounds = (n_idx < 1 \/ n_idx > n_size)
-- if b_out_of_bounds
-- if rejectTransitionResult = REJECT (ERROR . "StackOutOfBounds")
rule Expr_eval/stack-last-in-bounds:
p EC_0 ARCH_0 |- (typedExpressionIR_base . nameIR) # `(typeIR_base _) : EC_1 ARCH_1 expressionResult
-- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_base : EC_1 ARCH_1 (` headerStackValue_base)
-- if HEADER_STACK `[value_elem* `(n_idx ; n_size)] = headerStackValue_base
-- if nameIR = "last"
-- if b_out_of_bounds = (n_idx < 1 \/ n_idx > n_size)
-- if ~b_out_of_bounds
-- if n_idx_last = n_idx - 1
-- if value_last = value_elem*[n_idx_last]
-- if expressionResult = ` value_last
rule Expr_eval/stack-lastIndex:
p EC_0 ARCH_0 |- (typedExpressionIR_base . nameIR) # `(typeIR_base _) : EC_1 ARCH_1 expressionResult
-- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_base : EC_1 ARCH_1 (` headerStackValue_base)
-- if HEADER_STACK `[value_elem* `(n_idx ; _)] = headerStackValue_base
-- if nameIR = "lastIndex"
-- if n_idx_last = $max_nat([n_idx, 1]) - 1
-- if value_idx_last = 32 W n_idx_last
-- if expressionResult = ` value_idx_last
rule Expr_eval/struct:
p EC_0 ARCH_0 |- (typedExpressionIR_base . nameIR) # `(typeIR_base _) : EC_1 ARCH_1 expressionResult
-- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_base : EC_1 ARCH_1 (` structValue_base)
-- if STRUCT _ `{(value_field id_field ;)*} = structValue_base
-- if value_member = $assoc_<nameIR, value>(nameIR, (id_field, value_field)*)
-- if expressionResult = ` value_member
rule Expr_eval/header:
p EC_0 ARCH_0 |- (typedExpressionIR_base . nameIR) # `(typeIR_base _) : EC_1 ARCH_1 expressionResult
-- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_base : EC_1 ARCH_1 (` headerValue_base)
-- if HEADER _ `{_ ; (value_field id_field ;)*} = headerValue_base
-- if value_member = $assoc_<nameIR, value>(nameIR, (id_field, value_field)*)
-- if expressionResult = ` value_member
rule Expr_eval/headerunion:
p EC_0 ARCH_0 |- (typedExpressionIR_base . nameIR) # `(typeIR_base _) : EC_1 ARCH_1 expressionResult
-- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_base : EC_1 ARCH_1 (` headerUnionValue_base)
-- if HEADER_UNION _ `{(value_field id_field ;)*} = headerUnionValue_base
-- if value_member = $assoc_<nameIR, value>(nameIR, (id_field, value_field)*)
-- if expressionResult = ` value_member
rule Expr_eval/tableMetadataStructValue-hit:
p EC_0 ARCH_0 |- (typedExpressionIR_base . nameIR) # _ : EC_1 ARCH_1 expressionResult
-- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_base : EC_1 ARCH_1 (` tableMetadataStructValue_base)
-- if TABLE_STRUCT _ `{HIT boolValue_hit ; MISS _ ; ACTION_RUN _ ;} = tableMetadataStructValue_base
-- if nameIR = "hit"
-- if expressionResult = ` boolValue_hit
rule Expr_eval/tableMetadataStructValue-miss:
p EC_0 ARCH_0 |- (typedExpressionIR_base . nameIR) # _ : EC_1 ARCH_1 expressionResult
-- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_base : EC_1 ARCH_1 (` tableMetadataStructValue_base)
-- if TABLE_STRUCT _ `{HIT _ ; MISS boolValue_miss ; ACTION_RUN _ ;} = tableMetadataStructValue_base
-- if nameIR = "miss"
-- if expressionResult = ` boolValue_miss
rule Expr_eval/tableMetadataStructValue-action_run:
p EC_0 ARCH_0 |- (typedExpressionIR_base . nameIR) # _ : EC_1 ARCH_1 expressionResult
-- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR_base : EC_1 ARCH_1 (` tableMetadataStructValue_base)
-- if TABLE_STRUCT _ `{HIT _ ; MISS _ ; ACTION_RUN tableMetadataEnumValue ;} = tableMetadataStructValue_base
-- if nameIR = "action_run"
-- if expressionResult = ` tableMetadataEnumValue
14.14. Index access expressions
An index access expression accesses an element of a tuple or a header stack.
indexAccessExpression
: expression `[ expression ]
;
14.14.1. Type checking
After type checking, an index access expression is represented as:
indexAccessExpressionIR
: typedExpressionIR `[ typedExpressionIR ]
;
Click to view the specification source
rulegroup Expr_ok/indexAccessExpression: rule Expr_ok/tuple: p TC |- expression_base `[expression_index] : (typedExpressionIR_base `[typedExpressionIR_index_reduced]) # expressionNoteIR -- Expr_ok: p TC |- expression_base : typedExpressionIR_base -- Expr_ok: p TC |- expression_index : typedExpressionIR_index -- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base) -- if ctk_base = $ctk_of_typedExpressionIR(typedExpressionIR_base) -- if typeIR_index = $type_of_typedExpressionIR(typedExpressionIR_index) -- if ctk_index = $ctk_of_typedExpressionIR(typedExpressionIR_index) -- if typedExpressionIR_index_reduced = $reduce_serenum_unary(typedExpressionIR_index, $compat_array_index) -- if TUPLE `<typeIR_e*> = $unroll_typeIR(typeIR_base) -- if ctk_index = LCTK -- Expr_eval_lctk: p TC |- typedExpressionIR_index_reduced ~> integerValue_index -- if n_index = $nat_of_integerValue(integerValue_index) -- if n_index < |typeIR_e*| -- if expressionNoteIR = `(typeIR_e*[n_index] ctk_base) rule Expr_ok/headerStack-lctk: p TC |- expression_base `[expression_index] : (typedExpressionIR_base `[typedExpressionIR_index_reduced]) # expressionNoteIR -- Expr_ok: p TC |- expression_base : typedExpressionIR_base -- Expr_ok: p TC |- expression_index : typedExpressionIR_index -- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base) -- if ctk_base = $ctk_of_typedExpressionIR(typedExpressionIR_base) -- if typeIR_index = $type_of_typedExpressionIR(typedExpressionIR_index) -- if ctk_index = $ctk_of_typedExpressionIR(typedExpressionIR_index) -- if typedExpressionIR_index_reduced = $reduce_serenum_unary(typedExpressionIR_index, $compat_array_index) -- if typeIR `[n_size] = $unroll_typeIR(typeIR_base) -- if ctk_index = LCTK -- Expr_eval_lctk: p TC |- typedExpressionIR_index_reduced ~> integerValue_index -- if n_index = $nat_of_integerValue(integerValue_index) -- if n_index < n_size -- if expressionNoteIR = `(typeIR ctk_base) rule Expr_ok/headerStack-non-lctk: p TC |- expression_base `[expression_index] : (typedExpressionIR_base `[typedExpressionIR_index_reduced]) # expressionNoteIR -- Expr_ok: p TC |- expression_base : typedExpressionIR_base -- Expr_ok: p TC |- expression_index : typedExpressionIR_index -- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base) -- if ctk_base = $ctk_of_typedExpressionIR(typedExpressionIR_base) -- if typeIR_index = $type_of_typedExpressionIR(typedExpressionIR_index) -- if ctk_index = $ctk_of_typedExpressionIR(typedExpressionIR_index) -- if typedExpressionIR_index_reduced = $reduce_serenum_unary(typedExpressionIR_index, $compat_array_index) -- if typeIR `[n_size] = $unroll_typeIR(typeIR_base) -- if ctk_index =/= LCTK -- if ctk = $join_ctk(ctk_base, ctk_index) -- if expressionNoteIR = `(typeIR ctk)
14.14.2. Local compile-time evaluation
Click to view the specification source
rulegroup Expr_eval_lctk/indexAccessExpressionIR: rule Expr_eval_lctk/tuple: p TC |- (typedExpressionIR_base `[typedExpressionIR_index]) # _ ~> value -- Expr_eval_lctk: p TC |- typedExpressionIR_base ~> value_base -- Expr_eval_lctk: p TC |- typedExpressionIR_index ~> integerValue_index -- if TUPLE `(value_e*) = value_base -- if n_index = $nat_of_integerValue(integerValue_index) -- if n_index < |value_e*| -- if value = value_e*[n_index] rule Expr_eval_lctk/indexAccessExpressionIR-stack: p TC |- (typedExpressionIR_base `[typedExpressionIR_index]) # _ ~> value -- Expr_eval_lctk: p TC |- typedExpressionIR_base ~> value_base -- Expr_eval_lctk: p TC |- typedExpressionIR_index ~> integerValue_index -- if HEADER_STACK `[value_e* `(_ ; _)] = value_base -- if n_index = $nat_of_integerValue(integerValue_index) -- if n_index < |value_e*| -- if value = value_e*[n_index]
14.14.3. Compile-time evaluation
Click to view the specification source
rulegroup Expr_inst/indexAccessExpressionIR: rule Expr_inst/tuple: p IC STO_0 |- (typedExpressionIR_base `[typedExpressionIR_index]) # _ : STO_2 value -- Expr_inst: p IC STO_0 |- typedExpressionIR_base : STO_1 value_base -- Expr_inst: p IC STO_1 |- typedExpressionIR_index : STO_2 integerValue_index -- if TUPLE `(value_e*) = value_base -- if n_index = $nat_of_integerValue(integerValue_index) -- if n_index < |value_e*| -- if value = value_e*[n_index] rule Expr_inst/indexAccessExpressionIR-stack: p IC STO_0 |- (typedExpressionIR_base `[typedExpressionIR_index]) # _ : STO_2 value -- Expr_inst: p IC STO_0 |- typedExpressionIR_base : STO_1 value_base -- Expr_inst: p IC STO_1 |- typedExpressionIR_index : STO_2 integerValue_index -- if HEADER_STACK `[value_e* `(_ ; _)] = value_base -- if n_index = $nat_of_integerValue(integerValue_index) -- if n_index < |value_e*| -- if value = value_e*[n_index]
14.14.4. Runtime evaluation
Click to view the specification source
rulegroup Expr_eval/indexAccessExpressionIR: rule Expr_eval/abort: p EC_0 ARCH_0 |- (typedExpressionIR_base `[typedExpressionIR_index]) # _ : EC_1 ARCH_1 abortResult -- Exprs_eval: p EC_0 ARCH_0 |- [typedExpressionIR_base, typedExpressionIR_index] : EC_1 ARCH_1 abortResult rule Expr_eval/tuple: p EC_0 ARCH_0 |- (typedExpressionIR_base `[typedExpressionIR_index]) # _ : EC_1 ARCH_1 expressionResult -- Exprs_eval: p EC_0 ARCH_0 |- [typedExpressionIR_base, typedExpressionIR_index] : EC_1 ARCH_1 (` ([tupleValue_base, integerValue_index])) -- if TUPLE `(value_elem*) = tupleValue_base -- if n_index = $nat_of_integerValue(integerValue_index) -- if expressionResult = ` value_elem*[n_index] rule Expr_eval/headerStack: p EC_0 ARCH_0 |- (typedExpressionIR_base `[typedExpressionIR_index]) # _ : EC_1 ARCH_1 expressionResult -- Exprs_eval: p EC_0 ARCH_0 |- [typedExpressionIR_base, typedExpressionIR_index] : EC_1 ARCH_1 (` ([headerStackValue_base, integerValue_index])) -- if HEADER_STACK `[value_elem* `(_ ; n_size)] = headerStackValue_base -- if n_index = $nat_of_integerValue(integerValue_index) -- if n_idx = $ite<int>(n_index < n_size, n_index, n_size - 1) -- if expressionResult = ` value_elem*[n_idx]
14.15. Bitslice access expressions
A bitslice access expression is used to access a contiguous range of bits within a numeric value. The syntax for a bitslice access expression is as follows:
sliceAccessExpression
: expression `[ expression : expression ]
;
14.15.1. Type checking
After type checking, a bitslice access expression is represented as:
sliceAccessExpressionIR
: typedExpressionIR `[ typedExpressionIR : typedExpressionIR ]
;
Click to view the specification source
rulegroup Expr_ok/sliceAccessExpression: rule Expr_ok/sliceAccessExpression: p TC |- expression_base `[expression_hi : expression_lo] : (typedExpressionIR_base `[typedExpressionIR_hi_reduced : typedExpressionIR_lo_reduced]) # expressionNoteIR -- Expr_ok: p TC |- expression_base : typedExpressionIR_base -- Expr_ok: p TC |- expression_hi : typedExpressionIR_hi -- Expr_ok: p TC |- expression_lo : typedExpressionIR_lo -- if typedExpressionIR_base_reduced = $reduce_serenum_unary(typedExpressionIR_base, $compat_bitslice_base) -- if typeIR_base_reduced = $type_of_typedExpressionIR(typedExpressionIR_base_reduced) -- if ctk_base_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_base_reduced) -- if typedExpressionIR_hi_reduced = $reduce_serenum_unary(typedExpressionIR_hi, $compat_bitslice_index) -- if typedExpressionIR_lo_reduced = $reduce_serenum_unary(typedExpressionIR_lo, $compat_bitslice_index) -- if typeIR_hi_reduced = $type_of_typedExpressionIR(typedExpressionIR_hi_reduced) -- if ctk_hi_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_hi_reduced) -- if typeIR_lo_reduced = $type_of_typedExpressionIR(typedExpressionIR_lo_reduced) -- if ctk_lo_reduced = $ctk_of_typedExpressionIR(typedExpressionIR_lo_reduced) -- if ctk_hi_reduced = LCTK -- Expr_eval_lctk: p TC |- typedExpressionIR_hi_reduced ~> integerValue_hi -- if n_hi = $nat_of_integerValue(integerValue_hi) -- if ctk_lo_reduced = LCTK -- Expr_eval_lctk: p TC |- typedExpressionIR_lo_reduced ~> integerValue_lo -- if n_lo = $nat_of_integerValue(integerValue_lo) -- if $is_valid_bitslice(typeIR_base_reduced, n_lo, n_hi) -- if n_slice = n_hi - n_lo + 1 -- if typeIR = BIT `<n_slice> -- if expressionNoteIR = `(typeIR ctk_base_reduced)
14.15.2. Local compile-time evaluation
Click to view the specification source
rulegroup Expr_eval_lctk/sliceAccessExpressionIR: rule Expr_eval_lctk/sliceAccessExpressionIR: p TC |- (typedExpressionIR_base `[typedExpressionIR_hi : typedExpressionIR_lo]) # _ ~> $bitacc_op(value_base, value_hi, value_lo) -- Expr_eval_lctk: p TC |- typedExpressionIR_base ~> value_base -- Expr_eval_lctk: p TC |- typedExpressionIR_hi ~> value_hi -- Expr_eval_lctk: p TC |- typedExpressionIR_lo ~> value_lo
14.15.3. Compile-time evaluation
Click to view the specification source
rulegroup Expr_inst/sliceAccessExpressionIR: rule Expr_inst/sliceAccessExpressionIR: p IC STO_0 |- (typedExpressionIR_base `[typedExpressionIR_hi : typedExpressionIR_lo]) # _ : STO_3 $bitacc_op(value_base, value_hi, value_lo) -- Expr_inst: p IC STO_0 |- typedExpressionIR_base : STO_1 value_base -- Expr_inst: p IC STO_1 |- typedExpressionIR_hi : STO_2 value_hi -- Expr_inst: p IC STO_2 |- typedExpressionIR_lo : STO_3 value_lo
14.15.4. Runtime evaluation
Click to view the specification source
rulegroup Expr_eval/sliceAccessExpressionIR: rule Expr_eval/abort: p EC_0 ARCH_0 |- (typedExpressionIR_base `[typedExpressionIR_hi : typedExpressionIR_lo]) # _ : EC_1 ARCH_1 abortResult -- Exprs_eval: p EC_0 ARCH_0 |- [typedExpressionIR_base, typedExpressionIR_hi, typedExpressionIR_lo] : EC_1 ARCH_1 abortResult rule Expr_eval/cont: p EC_0 ARCH_0 |- (typedExpressionIR_base `[typedExpressionIR_hi : typedExpressionIR_lo]) # _ : EC_1 ARCH_1 expressionResult -- Exprs_eval: p EC_0 ARCH_0 |- [typedExpressionIR_base, typedExpressionIR_hi, typedExpressionIR_lo] : EC_1 ARCH_1 (` ([value_base, value_hi, value_lo])) -- if expressionResult = ` $bitacc_op(value_base, value_hi, value_lo)
14.16. Call expressions
Call expressions are used to invoke callables or constructors.
callExpression
: callTarget `( argumentList )
| callableTarget `< realTypeArgumentList > `( argumentList )
;
callTarget
: callableTarget
| constructorTarget
;
callableTarget = expression
constructorTarget = namedType
Details of how calls are resolved and evaluated are described in Chapter 18.
14.16.1. Type checking
After type checking, call expressions are represented as:
callExpressionIR
: constructorTargetIR `( argumentListIR )
| callableTargetIR `< typeArgumentListIR > `( argumentListIR )
;
callableTargetIR
: referenceExpressionIR
| typedExpressionIR . nameIR
| TYPE prefixedNameIR . nameIR
| `( callableTargetIR )
;
constructorTargetIR
: prefixedNameIR `< typeArgumentListIR >
;
Calling a callable
Click to view the specification source
rulegroup Expr_ok/callExpression-callable: rule Expr_ok/non-typeArgumentList-static_assert: p TC |- callableTarget `(argumentList) : literalExpressionIR # expressionNoteIR -- CallableTarget_ok: p TC |- callableTarget : callableTargetIR -- ArgumentList_ok: p TC |- argumentList : argumentIR* -- CallableType_ok: p TC |- callableTargetIR `<eps> `(argumentIR*) : callableTypeIR `<# typeId_infer*> `(# id_default* # id_optional*) -- Call_ok: p TC |- callableTypeIR `<eps # typeId_infer*> `(argumentIR* # id_default* # id_optional*) : typeIR_ret `<typeArgumentIR_inferred*> `(argumentIR_cast*) -- if typeIR_ret =/= VOID -- if ctk = $is_static_callableTypeIR(callableTypeIR) -- if $is_static_assert_callableTypeIR(callableTypeIR) -- if callExpressionIR = callableTargetIR `<typeArgumentIR_inferred*> `(argumentIR_cast*) -- if expressionNoteIR = `(typeIR_ret ctk) -- Expr_eval_lctk: p TC |- (callExpressionIR # expressionNoteIR) ~> (`B true) -- if literalExpressionIR = TRUE rule Expr_ok/non-typeArgumentList: p TC |- callableTarget `(argumentList) : callExpressionIR # expressionNoteIR -- CallableTarget_ok: p TC |- callableTarget : callableTargetIR -- ArgumentList_ok: p TC |- argumentList : argumentIR* -- CallableType_ok: p TC |- callableTargetIR `<eps> `(argumentIR*) : callableTypeIR `<# typeId_infer*> `(# id_default* # id_optional*) -- Call_ok: p TC |- callableTypeIR `<eps # typeId_infer*> `(argumentIR* # id_default* # id_optional*) : typeIR_ret `<typeArgumentIR_inferred*> `(argumentIR_cast*) -- if typeIR_ret =/= VOID -- if ctk = $is_static_callableTypeIR(callableTypeIR) -- if ~$is_static_assert_callableTypeIR(callableTypeIR) -- if callExpressionIR = callableTargetIR `<typeArgumentIR_inferred*> `(argumentIR_cast*) -- if expressionNoteIR = `(typeIR_ret ctk) rule Expr_ok/typeArgumentList: p TC |- callableTarget `<realTypeArgumentList> `(argumentList) : callExpressionIR # expressionNoteIR -- CallableTarget_ok: p TC |- callableTarget : callableTargetIR -- if realTypeArgument* = $flatten_realTypeArgumentList(realTypeArgumentList) -- TypeArguments_ok: p TC |- realTypeArgument* : typeArgumentIR* # typeId_impl* -- ArgumentList_ok: p TC |- argumentList : argumentIR* -- CallableType_ok: p TC |- callableTargetIR `<typeArgumentIR*> `(argumentIR*) : callableTypeIR `<# typeId_inserted*> `(# id_default* # id_optional*) -- if typeId_infer* = typeId_impl* ++ typeId_inserted* -- Call_ok: p TC |- callableTypeIR `<typeArgumentIR* # typeId_infer*> `(argumentIR* # id_default* # id_optional*) : typeIR_ret `<typeArgumentIR_inferred*> `(argumentIR_cast*) -- if typeIR_ret =/= VOID -- if ctk = $is_static_callableTypeIR(callableTypeIR) -- if callExpressionIR = callableTargetIR `<typeArgumentIR_inferred*> `(argumentIR_cast*) -- if expressionNoteIR = `(typeIR_ret ctk)
Calling a constructor
Click to view the specification source
rulegroup Expr_ok/callExpression-constructor: rule Expr_ok/prefixedTypeName: p TC |- prefixedTypeName `(argumentList) : callExpressionIR # expressionNoteIR -- ArgumentList_ok: p TC |- argumentList : argumentIR* -- if prefixedNameIR = $prefixedTypeName(prefixedTypeName) -- if constructorTargetIR = prefixedNameIR `<eps> -- ConstructorType_ok: p TC |- constructorTargetIR `(argumentIR*) : constructorTypeIR `<# typeId_impl*> `(# id_default* # id_optional*) -- Inst_ok: p TC ANON |- constructorTypeIR `<eps # typeId_impl*> `(argumentIR* # id_default* # id_optional*) : typeIR_object `<typeArgumentIR_inferred*> `(argumentIR_cast*) -- if $is_concrete_extern_object(typeIR_object) -- if callExpressionIR = (prefixedNameIR `<typeArgumentIR_inferred*>) `(argumentIR_cast*) -- if expressionNoteIR = `(typeIR_object CTK) rule Expr_ok/specializedType: p TC |- (prefixedTypeName `<typeArgumentList>) `(argumentList) : callExpressionIR # expressionNoteIR -- TypeArgumentList_ok: p TC |- typeArgumentList : typeArgumentIR* # typeId_impl* -- ArgumentList_ok: p TC |- argumentList : argumentIR* -- if prefixedNameIR = $prefixedTypeName(prefixedTypeName) -- if constructorTargetIR = prefixedNameIR `<typeArgumentIR*> -- ConstructorType_ok: p TC |- constructorTargetIR `(argumentIR*) : constructorTypeIR `<# typeId_inserted*> `(# id_default* # id_optional*) -- if typeId_infer* = typeId_impl* ++ typeId_inserted* -- Inst_ok: p TC ANON |- constructorTypeIR `<typeArgumentIR* # typeId_infer*> `(argumentIR* # id_default* # id_optional*) : typeIR_object `<typeArgumentIR_inferred*> `(argumentIR_cast*) -- if $is_concrete_extern_object(typeIR_object) -- if callExpressionIR = (prefixedNameIR `<typeArgumentIR_inferred*>) `(argumentIR_cast*) -- if expressionNoteIR = `(typeIR_object CTK)
14.16.2. Local compile-time evaluation
Certain call expressions can be evaluated during type checking:
Click to view the specification source
rulegroup Expr_eval_lctk/callExpressionIR: rule Expr_eval_lctk/static_assert: p TC |- (prefixedNameIR `<typeArgumentIR*> `(argumentIR*)) # _ ~> boolValue -- CallableType_ok: p TC |- prefixedNameIR `<typeArgumentIR*> `(argumentIR*) : externFunctionTypeIR `<# _> `(# id_default* # id_optional*) -- if EXTERN_FUNCTION "static_assert" `(parameterIR*) : BOOL = externFunctionTypeIR -- (if _ _ _ nameIR_param _ = parameterIR)* -- if TC_callee_0 = $empty_typingContext -- if GIVEN parameterIR_aligned* DEFAULT eps = $align_parameterListIR(parameterIR*, argumentIR*, id_default*, id_optional*) -- (Argument_eval_lctk: p TC |- argumentIR ~> value_argument)* -- (if _ direction_param_aligned typeIR_param_aligned nameIR_param_aligned _ = parameterIR_aligned)* -- (if (varTypeIR = direction_param_aligned typeIR_param_aligned LCTK value_argument))* -- if TC_callee_1 = $add_vars_t(LOCAL, TC_callee_0, nameIR_param_aligned*, varTypeIR*) -- ExternFunctionCall_eval_lctk: TC_callee_1 |- "static_assert" `(nameIR_param*) ~> boolValue -- if `B true = boolValue rule Expr_eval_lctk/size-expression: p TC |- ((typedExpressionIR_base . nameIR) `<_> `(_)) # _ ~> $sizeof(typeIR_base, nameIR) -- if _ # `(typeIR_base _) = typedExpressionIR_base -- if nameIR <- ["minSizeInBits", "minSizeInBytes", "maxSizeInBits", "maxSizeInBytes"] rule Expr_eval_lctk/size-type-monomorphic: p TC |- ((TYPE prefixedNameIR . nameIR) `<_> `(_)) # _ ~> $sizeof(typeIR_base, nameIR) -- if typeDefIR_base = $find_typeDef_t(p, TC, prefixedNameIR) -- if $is_monomorphic_typeDefIR(typeDefIR_base) -- if typeIR_base = $typeIR_of_typeDefIR(typeDefIR_base) -- if nameIR <- ["minSizeInBits", "minSizeInBytes", "maxSizeInBits", "maxSizeInBytes"] rule Expr_eval_lctk/size-type-polymorphic: p TC |- ((TYPE prefixedNameIR . nameIR) `<_> `(_)) # _ ~> $sizeof(typeIR_base, nameIR) -- if typeDefIR_base = $find_typeDef_t(p, TC, prefixedNameIR) -- if ~$is_monomorphic_typeDefIR(typeDefIR_base) -- if (eps, eps) = $typeParameterListIR_of_typeDefIR(typeDefIR_base) -- if typeIR_base = $typeIR_of_typeDefIR(typeDefIR_base) -- if nameIR <- ["minSizeInBits", "minSizeInBytes", "maxSizeInBits", "maxSizeInBytes"] rule Expr_eval_lctk/callExpressionIR-parenthesized: p TC |- ((`(callableTargetIR)) `<typeArgumentIR*> `(argumentIR*)) # `(typeIR ctk) ~> value -- Expr_eval_lctk: p TC |- (callableTargetIR `<typeArgumentIR*> `(argumentIR*)) # `(typeIR ctk) ~> value
14.16.3. Compile-time evaluation
Call expressions may also be evaluated at compile time when instantiation phase takes place. A constructor call evaluates to an instance of the constructed type, while a callable call evaluates to the result of the call.
Calling a callable
Click to view the specification source
rulegroup Expr_inst/callExpressionIR-callable: rule Expr_inst/expression: p IC STO |- ((typedExpressionIR_base . nameIR) `<_> `(_)) # _ : STO $sizeof(typeIR_base, nameIR) -- if _ # `(typeIR_base _) = typedExpressionIR_base -- if nameIR <- ["minSizeInBits", "minSizeInBytes", "maxSizeInBits", "maxSizeInBytes"] rule Expr_inst/type-monomorphic: p IC STO |- ((TYPE prefixedNameIR . nameIR) `<_> `(_)) # _ : STO $sizeof(typeIR_base, nameIR) -- if typeDefIR_base = $find_typeDef_i(p, IC, prefixedNameIR) -- if $is_monomorphic_typeDefIR(typeDefIR_base) -- if typeIR_base = $typeIR_of_typeDefIR(typeDefIR_base) -- if nameIR <- ["minSizeInBits", "minSizeInBytes", "maxSizeInBits", "maxSizeInBytes"] rule Expr_inst/type-polymorphic: p IC STO |- ((TYPE prefixedNameIR . nameIR) `<_> `(_)) # _ : STO $sizeof(typeIR_base, nameIR) -- if typeDefIR_base = $find_typeDef_i(p, IC, prefixedNameIR) -- if ~$is_monomorphic_typeDefIR(typeDefIR_base) -- if (eps, eps) = $typeParameterListIR_of_typeDefIR(typeDefIR_base) -- if typeIR_base = $typeIR_of_typeDefIR(typeDefIR_base) -- if nameIR <- ["minSizeInBits", "minSizeInBytes", "maxSizeInBits", "maxSizeInBytes"]
Calling a constructor
Click to view the specification source
rulegroup Expr_inst/callExpressionIR-constructor: rule Expr_inst/callExpressionIR-constructor: p IC STO_0 |- ((prefixedNameIR `<typeArgumentListIR>) `(argumentListIR)) # _ : STO_2 value_ref -- Constructor_inst: p IC |- prefixedNameIR `<typeArgumentListIR> `(argumentListIR) : constructorDef `<typeArgumentListIR_inst> `(# id_default* # id_optional*) -- Constructor_call: p IC STO_0 |- constructorDef `<typeArgumentListIR_inst> `(argumentListIR # id_default* # id_optional*) : STO_1 object -- if STO_2 = $add_store(STO_1, IC.PATH, object) -- if value_ref = REF IC.PATH
Parenthesized call
Click to view the specification source
rulegroup Expr_inst/callExpressionIR-parenthesized: rule Expr_inst/callExpressionIR-parenthesized: p IC STO_0 |- ((`(callableTargetIR)) `<typeArgumentListIR> `(argumentListIR)) # expressionNoteIR : STO_1 value -- Expr_inst: p IC STO_0 |- (callableTargetIR `<typeArgumentListIR> `(argumentListIR)) # expressionNoteIR : STO_1 value
14.16.4. Runtime evaluation
Click to view the specification source
rulegroup Expr_eval/callExpressionIR: rule Expr_eval/callee-abort: p EC_0 ARCH_0 |- (callableTargetIR `<typeArgumentIR*> `(argumentIR*)) # _ : EC_1 ARCH_1 abortResult -- Callee_eval: p EC_0 ARCH_0 |- callableTargetIR `<_> `(argumentIR*) : EC_1 ARCH_1 abortResult rule Expr_eval/callee-cont-call-abort: p EC_0 ARCH_0 |- (callableTargetIR `<typeArgumentIR*> `(argumentIR*)) # _ : EC_2 ARCH_2 abortResult -- Callee_eval: p EC_0 ARCH_0 |- callableTargetIR `<_> `(argumentIR*) : EC_1 ARCH_1 (` callee) -- Call_eval: p EC_1 ARCH_1 |- callee @ `<typeArgumentIR*> `(argumentIR*) : EC_2 ARCH_2 abortResult rule Expr_eval/callee-cont-call-return: p EC_0 ARCH_0 |- (callableTargetIR `<typeArgumentIR*> `(argumentIR*)) # _ : EC_2 ARCH_2 (` value) -- Callee_eval: p EC_0 ARCH_0 |- callableTargetIR `<_> `(argumentIR*) : EC_1 ARCH_1 (` callee) -- Call_eval: p EC_1 ARCH_1 |- callee @ `<typeArgumentIR*> `(argumentIR*) : EC_2 ARCH_2 (RETURN value)
14.17. Parenthesized expressions
Expressions may be enclosed in parentheses to explicitly specify the order of evaluation.
parenthesizedExpression
: `( expression )
;
14.17.1. Type checking
After type checking, parenthesized expressions have the form:
parenthesizedExpressionIR
: `( typedExpressionIR )
;
Click to view the specification source
rulegroup Expr_ok/parenthesizedExpression: rule Expr_ok/parenthesizedExpression: p TC |- `(expression) : (`(typedExpressionIR)) # expressionNoteIR -- Expr_ok: p TC |- expression : typedExpressionIR -- if typeIR = $type_of_typedExpressionIR(typedExpressionIR) -- if ctk = $ctk_of_typedExpressionIR(typedExpressionIR) -- if expressionNoteIR = `(typeIR ctk)
14.17.2. Local compile-time evaluation
Click to view the specification source
rulegroup Expr_eval_lctk/parenthesizedExpressionIR: rule Expr_eval_lctk/parenthesizedExpressionIR: p TC |- (`(typedExpressionIR)) # _ ~> value -- Expr_eval_lctk: p TC |- typedExpressionIR ~> value
14.17.3. Compile-time evaluation
Click to view the specification source
rulegroup Expr_inst/parenthesizedExpressionIR: rule Expr_inst/parenthesizedExpressionIR: p IC STO_0 |- (`(typedExpressionIR)) # _ : STO_1 value -- Expr_inst: p IC STO_0 |- typedExpressionIR : STO_1 value
14.17.4. Runtime evaluation
Click to view the specification source
rulegroup Expr_eval/parenthesizedExpressionIR: rule Expr_eval/parenthesizedExpressionIR: p EC_0 ARCH_0 |- (`(typedExpressionIR)) # _ : EC_1 ARCH_1 expressionResult -- Expr_eval: p EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 expressionResult
15. Abstract parser machine
A parser declaration introduces a constructor for a parser object. See Section 10.4 and Section 11.11 for details.
parserDeclaration
: annotationList PARSER name typeParameterListOpt `( parameterList )
constructorParameterListOpt
`{ parserLocalDeclarationList parserStateList }
;
parserLocalDeclaration
: constantDeclaration
| instantiation
| variableDeclaration
| valueSetDeclaration
;
parserState
: annotationList STATE name `{ parserStatementList transitionStatement }
;
This section describes the P4 constructs specific to parsing network packets.
15.1. Parser state machine
A P4 parser describes a state machine with one start state and two final
states. The start state is always named start. The two final states are named
accept (indicating successful parsing) and reject (indicating a parsing failure).
The start state is part of the parser, while the accept and reject states
are distinct from the states provided by the programmer and are logically
outside of the parser. Figure 12 illustrates the general
structure of a parser state machine.
The semantics of a P4 parser can be formulated in terms of an abstract machine
that manipulates a ParserModel data structure. This section describes this
abstract machine in pseudo-code.
A parser starts execution in the start state and ends execution when one of
the reject or accept states has been reached.
ParserModel {
error parseError;
onPacketArrival(packet p) {
ParserModel.parseError = error.NoError;
goto start;
}
}
An architecture must specify the behavior when the accept and reject states
are reached. For example, an architecture may specify that all packets reaching
the reject state are dropped without further processing. Alternatively, it
may specify that such packets are passed to the next block after the parser,
with intrinsic metadata indicating that the parser reached the reject state,
along with the error recorded.
15.1.1. Sub-parsers
P4 also allows parsers to invoke the services of other parsers, similar to
subroutines. To invoke the services of another parser, the sub-parser must be
first instantiated; the services of an instance are invoked by calling it using
its apply method.
The following example shows a sub-parser invocation:
parser callee(packet_in packet, out IPv4 ipv4) { /* body omitted */ }
parser caller(packet_in packet, out Headers h) {
callee() subparser; // instance of callee
state subroutine {
subparser.apply(packet, h.ipv4); // invoke sub-parser
transition accept; // accept if sub-parser ends in accept state
}
}
The semantics of a sub-parser invocation can be described as follows:
-
The state invoking the sub-parser is split into two half-states at the parser invocation statement.
-
The top half includes a transition to the sub-parser
startstate. -
The sub-parser’s
acceptstate is identified with the bottom half of the current state -
The sub-parser’s
rejectstate is identified with the reject state of the current parser.
Figure 13 shows a diagram of this process.
Note that since P4 requires definitions to precede uses, it is impossible to create recursive (or mutually recursive) parsers.
When a parser is instantiated, local instantiations of stateful objects are evaluated recursively. That is, each instantiation of a parser has a unique set of local parser value sets, extern objects, inner parser instances, etc. Thus, in general, invoking a parser instance twice is not the same as invoking two copies of the same parser instance. Note however that local variables do not persist across invocations of the parser. This semantics also applies to direct invocation (see Section 13.5).
Architectures may impose (static or dynamic) constraints on the number of
parser states that can be traversed for processing each packet. For example, a
compiler for a specific target may reject parsers containing loops that cannot
be unrolled at compilation time or that may contain cycles that do not advance
the cursor. If a parser aborts execution dynamically because it exceeded the
time budget allocated for parsing, the parser should transition to reject and
set the standard error error.ParserTimeout.
15.1.2. Runtime evaluation of a parser
At runtime, a parser is evaluated by invoking its apply method. The following
algorithm describes the evaluation of a parser apply method invocation:
Click to view the specification source
rulegroup Call_eval/parserApplyMethodCallee:
rule Call_eval/copyin-reject:
p EC_0 ARCH_0 |- parserApplyMethodCallee @ `<typeArgumentListIR> `(argumentListIR) : EC_1 ARCH_1 rejectTransitionResult
-- if PARSER _ . APPLY `(parameterListIR # id_default*) `{theta ; frame ; parserLocalDeclarationListIR ; stateEnv} = parserApplyMethodCallee
-- if EC_callee_0 = $inherit_e(GLOBAL, EC_0)
-- if EC_callee_1 = EC_callee_0[BLOCK.TYPE = theta][BLOCK.FRAME = frame][BLOCK.STATE = stateEnv]
-- if GIVEN parameterIR_aligned* DEFAULT parameterIR_default* = $align_parameterListIR(parameterListIR, argumentListIR, id_default*, eps)
-- if p_shared = GLOBAL
-- Copy_in: p_shared ARCH_0 |- p EC_0 parameterIR_aligned* @ BLOCK EC_callee_1 argumentListIR ~> ARCH_1 EC_1 EC_callee_2 # rejectTransitionResult
rule Call_eval/copyin-cont-parserLocalDeclarationListIR-reject:
p EC_0 ARCH_0 |- parserApplyMethodCallee @ `<typeArgumentListIR> `(argumentListIR) : EC_2 ARCH_2 rejectTransitionResult
-- if PARSER _ . APPLY `(parameterListIR # id_default*) `{theta ; frame ; parserLocalDeclarationListIR ; stateEnv} = parserApplyMethodCallee
-- if EC_callee_0 = $inherit_e(GLOBAL, EC_0)
-- if EC_callee_1 = EC_callee_0[BLOCK.TYPE = theta][BLOCK.FRAME = frame][BLOCK.STATE = stateEnv]
-- if GIVEN parameterIR_aligned* DEFAULT parameterIR_default* = $align_parameterListIR(parameterListIR, argumentListIR, id_default*, eps)
-- if p_shared = GLOBAL
-- Copy_in: p_shared ARCH_0 |- p EC_0 parameterIR_aligned* @ BLOCK EC_callee_1 argumentListIR ~> ARCH_1 EC_1 EC_callee_2 # (` storageReference?*)
-- Copy_in_default: parameterIR_default* @ BLOCK EC_callee_2 ~> EC_callee_3
-- ParserLocalDecls_eval: EC_callee_3 ARCH_1 |- parserLocalDeclarationListIR : EC_callee_4 ARCH_2 rejectTransitionResult
-- Copy_out: p_shared ARCH_2 |- p EC_1 parameterIR_aligned* @ BLOCK EC_callee_4 storageReference?* ~> EC_2
rule Call_eval/copyin-cont-parserLocalDeclarationListIR-cont-transition-reject:
p EC_0 ARCH_0 |- parserApplyMethodCallee @ `<typeArgumentListIR> `(argumentListIR) : EC_2 ARCH_3 rejectTransitionResult
-- if PARSER _ . APPLY `(parameterListIR # id_default*) `{theta ; frame ; parserLocalDeclarationListIR ; stateEnv} = parserApplyMethodCallee
-- if EC_callee_0 = $inherit_e(GLOBAL, EC_0)
-- if EC_callee_1 = EC_callee_0[BLOCK.TYPE = theta][BLOCK.FRAME = frame][BLOCK.STATE = stateEnv]
-- if GIVEN parameterIR_aligned* DEFAULT parameterIR_default* = $align_parameterListIR(parameterListIR, argumentListIR, id_default*, eps)
-- if p_shared = GLOBAL
-- Copy_in: p_shared ARCH_0 |- p EC_0 parameterIR_aligned* @ BLOCK EC_callee_1 argumentListIR ~> ARCH_1 EC_1 EC_callee_2 # (` storageReference?*)
-- Copy_in_default: parameterIR_default* @ BLOCK EC_callee_2 ~> EC_callee_3
-- ParserLocalDecls_eval: EC_callee_3 ARCH_1 |- parserLocalDeclarationListIR : EC_callee_4 ARCH_2 continueEmptyResult
-- ParserState_trans: EC_callee_4 ARCH_2 |- TRANSITION "start" : EC_callee_5 ARCH_3 rejectTransitionResult
-- Copy_out: p_shared ARCH_3 |- p EC_1 parameterIR_aligned* @ BLOCK EC_callee_5 storageReference?* ~> EC_2
rule Call_eval/parserLocalDeclarationListIR-cont-transition-accept:
p EC_0 ARCH_0 |- parserApplyMethodCallee @ `<typeArgumentListIR> `(argumentListIR) : EC_2 ARCH_3 (RETURN eps)
-- if PARSER _ . APPLY `(parameterListIR # id_default*) `{theta ; frame ; parserLocalDeclarationListIR ; stateEnv} = parserApplyMethodCallee
-- if EC_callee_0 = $inherit_e(GLOBAL, EC_0)
-- if EC_callee_1 = EC_callee_0[BLOCK.TYPE = theta][BLOCK.FRAME = frame][BLOCK.STATE = stateEnv]
-- if GIVEN parameterIR_aligned* DEFAULT parameterIR_default* = $align_parameterListIR(parameterListIR, argumentListIR, id_default*, eps)
-- if p_shared = GLOBAL
-- Copy_in: p_shared ARCH_0 |- p EC_0 parameterIR_aligned* @ BLOCK EC_callee_1 argumentListIR ~> ARCH_1 EC_1 EC_callee_2 # (` storageReference?*)
-- Copy_in_default: parameterIR_default* @ BLOCK EC_callee_2 ~> EC_callee_3
-- ParserLocalDecls_eval: EC_callee_3 ARCH_1 |- parserLocalDeclarationListIR : EC_callee_4 ARCH_2 continueEmptyResult
-- ParserState_trans: EC_callee_4 ARCH_2 |- TRANSITION "start" : EC_callee_5 ARCH_3 ACCEPT
-- Copy_out: p_shared ARCH_3 |- p EC_1 parameterIR_aligned* @ BLOCK EC_callee_5 storageReference?* ~> EC_2
15.1.3. Parser state transitions
At runtime, parser state transitions are defined as:
Click to view the specification source
relation ParserState_trans: evalContext arch |- TRANSITION nameIR : evalContext arch transitionResult
The result of a transition is:
transitionResult
: acceptTransitionResult
| rejectTransitionResult
| stateTransitionResult
;
acceptTransitionResult
: ACCEPT
;
rejectTransitionResult
: REJECT errorValue
;
stateTransitionResult
: STATE id
;
Click to view the specification source
rulegroup ParserState_trans: rule ParserState_trans/accept: EC_0 ARCH_0 |- TRANSITION nameIR : EC_1 ARCH_1 ACCEPT -- if parserStateIR = $find_parserState_e(EC_0, nameIR) -- ParserState_eval: EC_0 ARCH_0 |- parserStateIR : EC_1 ARCH_1 ACCEPT rule ParserState_trans/reject: EC_0 ARCH_0 |- TRANSITION nameIR : EC_1 ARCH_1 rejectTransitionResult -- if parserStateIR = $find_parserState_e(EC_0, nameIR) -- ParserState_eval: EC_0 ARCH_0 |- parserStateIR : EC_1 ARCH_1 rejectTransitionResult rule ParserState_trans/state: EC_0 ARCH_0 |- TRANSITION nameIR : EC_2 ARCH_2 transitionResult -- if parserStateIR = $find_parserState_e(EC_0, nameIR) -- ParserState_eval: EC_0 ARCH_0 |- parserStateIR : EC_1 ARCH_1 (STATE nameIR_next) -- ParserState_trans: EC_1 ARCH_1 |- TRANSITION nameIR_next : EC_2 ARCH_2 transitionResult
The following sections describe the components of a parser state transition in more detail.
15.2. Parser local declarations
Parser local declarations are defined as follows:
parserLocalDeclaration
: constantDeclaration
| instantiation
| variableDeclaration
| valueSetDeclaration
;
parserLocalDeclarationList
: /* empty */
| parserLocalDeclarationList parserLocalDeclaration
;
15.2.1. Semantics of parser local declarations
15.2.1.1. Type checking
Click to view the specification source
relation ParserLocalDecl_ok: typingContext |- parserLocalDeclaration : typingContext parserLocalDeclarationIR
After type checking, parser local declarations are represented in P4IR as follows:
parserLocalDeclarationIR
: constantDeclarationIR
| instantiationIR
| variableDeclarationIR
| valueSetDeclarationIR
;
A list of parser local declarations is type checked by:
Click to view the specification source
relation ParserLocalDeclList_ok: typingContext |- parserLocalDeclarationList : typingContext parserLocalDeclarationIR*
Click to view the specification source
rulegroup ParserLocalDeclList_ok: rule ParserLocalDeclList_ok: TC_0 |- parserLocalDeclarationList : TC_1 parserLocalDeclarationIR* -- if parserLocalDeclaration* = $flatten_parserLocalDeclarationList(parserLocalDeclarationList) -- ParserLocalDecls_ok: TC_0 |- parserLocalDeclaration* : TC_1 parserLocalDeclarationIR*
Click to view the specification source
relation ParserLocalDecls_ok: typingContext |- parserLocalDeclaration* : typingContext parserLocalDeclarationIR*
Click to view the specification source
rulegroup ParserLocalDecls_ok: rule ParserLocalDecls_ok/nil: TC |- eps : TC eps rule ParserLocalDecls_ok/cons: TC_0 |- parserLocalDeclaration_h :: parserLocalDeclaration_t* : TC_2 (parserLocalDeclarationIR_h :: parserLocalDeclarationIR_t*) -- ParserLocalDecl_ok: TC_0 |- parserLocalDeclaration_h : TC_1 parserLocalDeclarationIR_h -- ParserLocalDecls_ok: TC_1 |- parserLocalDeclaration_t* : TC_2 parserLocalDeclarationIR_t*
15.2.1.2. Compile-time evaluation
Click to view the specification source
relation ParserLocalDecl_inst: instContext store |- parserLocalDeclarationIR : instContext store parserLocalDeclarationIR
A list of parser local declarations is compile-time evaluated by:
Click to view the specification source
relation ParserLocalDecls_inst: instContext store |- parserLocalDeclarationIR* : instContext store parserLocalDeclarationIR*
Click to view the specification source
rulegroup ParserLocalDecls_inst: rule ParserLocalDecls_inst/nil: IC STO |- eps : IC STO eps rule ParserLocalDecls_inst/cons: IC_0 STO_0 |- parserLocalDeclarationIR_h :: parserLocalDeclarationIR_t* : IC_2 STO_2 (parserLocalDeclarationIR_h_inst :: parserLocalDeclarationIR_t_inst*) -- ParserLocalDecl_inst: IC_0 STO_0 |- parserLocalDeclarationIR_h : IC_1 STO_1 parserLocalDeclarationIR_h_inst -- ParserLocalDecls_inst: IC_1 STO_1 |- parserLocalDeclarationIR_t* : IC_2 STO_2 parserLocalDeclarationIR_t_inst*
15.2.1.3. Runtime evaluation
Click to view the specification source
relation ParserLocalDecl_eval: evalContext arch |- parserLocalDeclarationIR : evalContext arch parserDeclarationResult
Evaluation of a parser local declaration yields:
parserDeclarationResult = parserStatementResult
parserStatementResult
: continueEmptyResult
| rejectTransitionResult
;
continueEmptyResult
: /* empty */
;
rejectTransitionResult
: REJECT errorValue
;
A list of parser local declarations is runtime evaluated by:
Click to view the specification source
relation ParserLocalDecls_eval: evalContext arch |- parserLocalDeclarationIR* : evalContext arch parserDeclarationResult
Click to view the specification source
rulegroup ParserLocalDecls_eval: rule ParserLocalDecls_eval/nil: EC ARCH |- eps : EC ARCH `EMPTY rule ParserLocalDecls_eval/cons-reject: EC_0 ARCH_0 |- parserLocalDeclarationIR_h :: parserLocalDeclarationListIR_t : EC_1 ARCH_1 rejectTransitionResult -- ParserLocalDecl_eval: EC_0 ARCH_0 |- parserLocalDeclarationIR_h : EC_1 ARCH_1 rejectTransitionResult rule ParserLocalDecls_eval/cons-cont: EC_0 ARCH_0 |- parserLocalDeclarationIR_h :: parserLocalDeclarationListIR_t : EC_2 ARCH_2 parserStatementResult -- ParserLocalDecl_eval: EC_0 ARCH_0 |- parserLocalDeclarationIR_h : EC_1 ARCH_1 continueEmptyResult -- ParserLocalDecls_eval: EC_1 ARCH_1 |- parserLocalDeclarationListIR_t : EC_2 ARCH_2 parserStatementResult
The subsequent sections describe each kind of declaration in detail.
15.2.2. Constant declarations
See Section 11.3 for general information about constant declarations.
15.2.2.1. Type checking
Click to view the specification source
rulegroup ParserLocalDecl_ok/constantDeclaration: rule ParserLocalDecl_ok/constantDeclaration: TC_0 |- constantDeclaration : TC_1 constantDeclarationIR -- ConstDecl_ok: BLOCK TC_0 |- constantDeclaration : TC_1 constantDeclarationIR
15.2.2.2. Compile-time evaluation
Click to view the specification source
rulegroup ParserLocalDecl_inst/constantDeclarationIR: rule ParserLocalDecl_inst/constantDeclarationIR: IC_0 STO |- constantDeclarationIR : IC_1 STO constantDeclarationIR -- ConstDecl_inst: BLOCK IC_0 |- constantDeclarationIR : IC_1
15.2.2.3. Runtime evaluation
Click to view the specification source
rulegroup ParserLocalDecl_eval/constantDeclarationIR: rule ParserLocalDecl_eval/constantDeclarationIR: EC_0 ARCH |- constantDeclarationIR : EC_1 ARCH `EMPTY -- ConstDecl_eval: BLOCK EC_0 |- constantDeclarationIR : EC_1
15.2.3. Instantiations
See Section 11.4 for general information about instantiations.
15.2.3.1. Type checking
Click to view the specification source
rulegroup ParserLocalDecl_ok/instantiation: rule ParserLocalDecl_ok/instantiation: TC_0 |- instantiation : TC_1 instantiationIR -- InstDecl_ok: BLOCK TC_0 |- instantiation : TC_1 instantiationIR
15.2.3.2. Compile-time evaluation
Click to view the specification source
rulegroup ParserLocalDecl_inst/instantiationIR: rule ParserLocalDecl_inst/instantiationIR: IC_0 STO_0 |- instantiationIR : IC_1 STO_1 constantDeclarationIR_inst -- InstDecl_inst: BLOCK IC_0 STO_0 |- instantiationIR : IC_1 STO_1 constantDeclarationIR_inst
15.2.4. Variable declarations
See Section 11.2 for general information about variable declarations.
15.2.4.1. Type checking
Click to view the specification source
rulegroup ParserLocalDecl_ok/variableDeclaration: rule ParserLocalDecl_ok/variableDeclaration: TC_0 |- variableDeclaration : TC_1 variableDeclarationIR -- VarDecl_ok: BLOCK TC_0 |- variableDeclaration : TC_1 variableDeclarationIR
15.2.4.2. Compile-time evaluation
Click to view the specification source
rulegroup ParserLocalDecl_inst/variableDeclarationIR: rule ParserLocalDecl_inst/variableDeclarationIR: IC STO |- variableDeclarationIR : IC STO variableDeclarationIR
15.2.4.3. Runtime evaluation
Click to view the specification source
rulegroup ParserLocalDecl_eval/variableDeclarationIR: rule ParserLocalDecl_eval/reject: EC_0 ARCH_0 |- variableDeclarationIR : EC_1 ARCH_1 rejectTransitionResult -- VarDecl_eval: BLOCK EC_0 ARCH_0 |- variableDeclarationIR : EC_1 ARCH_1 rejectTransitionResult rule ParserLocalDecl_eval/cont: EC_0 ARCH_0 |- variableDeclarationIR : EC_1 ARCH_1 `EMPTY -- VarDecl_eval: BLOCK EC_0 ARCH_0 |- variableDeclarationIR : EC_1 ARCH_1 `EMPTY
15.2.5. Parser value set declarations
Parser value sets are declared with the syntax:
valueSetDeclaration
: annotationList VALUE_SET `< valueSetType > `( expression ) name ;
;
valueSetType
: baseType
| tupleType
| prefixedTypeName
;
See Section 10.7 for general information about value sets.
15.2.5.1. Type checking
Click to view the specification source
rulegroup ParserLocalDecl_ok/valueSetDeclaration: rule ParserLocalDecl_ok/valueSetDeclaration: TC_0 |- annotationList VALUE_SET `<valueSetType> `(expression) name ; : TC_1 valueSetDeclarationIR -- Type_ok: BLOCK TC_0 |- valueSetType : typeIR # eps -- Type_wf: $bound(BLOCK, TC_0) |- SET `<typeIR> -- Expr_ok: BLOCK TC_0 |- expression : typedExpressionIR -- if DYN =/= $ctk_of_typedExpressionIR(typedExpressionIR) -- if nameIR = $name(name) -- if varTypeIR = `EMPTY (SET `<typeIR>) CTK eps -- if TC_1 = $add_var_t(BLOCK, TC_0, nameIR, varTypeIR) -- if valueSetDeclarationIR = annotationList VALUE_SET `<typeIR> `(typedExpressionIR) nameIR ;
15.2.5.2. Compile-time evaluation
Click to view the specification source
rulegroup ParserLocalDecl_inst/valueSetDeclarationIR:
rule ParserLocalDecl_inst/valueSetDeclarationIR:
IC STO_0 |- annotationList VALUE_SET `<typeIR> `(typedExpressionIR_size) nameIR ; : IC STO_2 constantDeclarationIR
-- Expr_inst: BLOCK IC STO_0 |- typedExpressionIR_size : STO_1 integerValue_size
-- if n_size = $nat_of_integerValue(integerValue_size)
-- if typeIR_subst = $subst_type_i(BLOCK, IC, typeIR)
-- if value* = $repeat_<value>($default(typeIR_subst), n_size)
-- if valueSetObject = VALUE_SET `{value* `(n_size)}
-- if objectId = IC.PATH ++ nameIR
-- if STO_2 = $add_store(STO_1, objectId, valueSetObject)
-- if typeIR_set = SET `<typeIR>
-- if constantDeclarationIR = `EMPTY CONST typeIR_set nameIR (= `VALUE (REF objectId)) ;
After compile-time evaluation, parser value set declarations are represented in P4IR as follows:
valueSetDeclarationIR
: annotationList VALUE_SET `< typeIR > `( typedExpressionIR ) nameIR ;
;
15.3. Parser states
A parser state is declared with the following syntax:
parserState
: annotationList STATE name `{ parserStatementList transitionStatement }
;
Each state has a name and a body. The body consists of a sequence of statements that describe the processing performed when the parser transitions to that state, including:
-
Local variable declarations,
-
Assignment statements,
-
Method calls, which serve several purposes:
-
Invoking functions (e.g., using
verifyto check the validity of data already parsed), and -
Invoking methods (e.g., extracting data out of packets or computing checksums) and other parsers (see Section 15.1.1), and
-
-
Conditional statements,
-
Transitions to other states (discussed in Section 15.5).
Architectures may place restrictions on the expressions and statements that can be used in a parser—e.g., they may forbid the use of operations such as multiplication or place restrictions on the number of local variables that may be used.
15.3.1. Type checking
A list of parser states is type checked with the following relation:
Click to view the specification source
relation ParserStateList_ok: typingContext |- parserStateList : parserStateIR*
Click to view the specification source
rulegroup ParserStateList_ok:
rule ParserStateList_ok:
TC |- parserStateList : parserStateIR*
-- if parserState* = $flatten_parserStateList(parserStateList)
-- (if (_ STATE name_state `{_ _} = parserState))*
-- (if (nameIR_state = $name(name_state)))*
-- if $distinct_<nameIR>(nameIR_state*)
-- if "start" <- nameIR_state*
-- if ~("accept" <- nameIR_state*) /\ ~("reject" <- nameIR_state*)
-- if nameIR_state_impl* = "accept" :: "reject" :: nameIR_state*
-- (ParserState_ok: TC nameIR_state_impl* |- parserState : parserStateIR)*
A parser state is type checked with the following relation:
Click to view the specification source
relation ParserState_ok: typingContext nameIR* |- parserState : parserStateIR
Click to view the specification source
rulegroup ParserState_ok:
rule ParserState_ok:
TC_0 nameIR_state* |- parserState : parserStateIR
-- if annotationList STATE name `{parserStatementList transitionStatement} = parserState
-- if nameIR = $name(name)
-- if TC_1 = $enter_t(TC_0)
-- if parserStatement* = $flatten_parserStatementList(parserStatementList)
-- ParserStmts_ok: TC_1 |- parserStatement* : TC_2 parserStatementIR*
-- ParserTransition_ok: TC_2 nameIR_state* |- transitionStatement : transitionStatementIR
-- if TC_3 = $exit_t(TC_2)
-- if parserStateIR = annotationList STATE nameIR `{parserStatementIR* transitionStatementIR}
15.3.2. Compile-time evaluation
A list parser states is compile-time evaluated with the following relation:
Click to view the specification source
relation ParserStates_inst: instContext store |- parserStateIR* : instContext store
Click to view the specification source
rulegroup ParserStates_inst: rule ParserStates_inst/nil: IC STO |- eps : IC STO rule ParserStates_inst/cons: IC_0 STO_0 |- parserStateIR_h :: parserStateIR_t* : IC_2 STO_2 -- ParserState_inst: IC_0 STO_0 |- parserStateIR_h : IC_1 STO_1 -- ParserStates_inst: IC_1 STO_1 |- parserStateIR_t* : IC_2 STO_2
A parser state is compile-time evaluated with the following relation:
Click to view the specification source
relation ParserState_inst: instContext store |- parserStateIR : instContext store
Click to view the specification source
rulegroup ParserState_inst:
rule ParserState_inst:
IC_0 STO_0 |- parserStateIR : IC_2 STO_1
-- if annotationList STATE nameIR `{parserStatementListIR transitionStatementIR} = parserStateIR
-- ParserStmts_inst: IC_0 STO_0 |- parserStatementListIR : IC_1 STO_1 parserStatementListIR_inst
-- if parserStateIR_inst = annotationList STATE nameIR `{parserStatementListIR_inst transitionStatementIR}
-- if IC_2 = $add_parserState_i(IC_1, nameIR, parserStateIR_inst)
15.3.3. Runtime evaluation
A parser state is runtime evaluated with the following relation:
Click to view the specification source
relation ParserState_eval: evalContext arch |- parserStateIR : evalContext arch transitionResult
The result of evaluation is a state transition,
transitionResult
: acceptTransitionResult
| rejectTransitionResult
| stateTransitionResult
;
Click to view the specification source
rulegroup ParserState_eval:
rule ParserState_eval/reject:
EC_0 ARCH_0 |- parserStateIR : EC_1 ARCH_1 rejectTransitionResult
-- if annotationList STATE nameIR `{parserStatementListIR transitionStatementIR} = parserStateIR
-- ParserStmts_eval: EC_0 ARCH_0 |- parserStatementListIR : EC_1 ARCH_1 rejectTransitionResult
rule ParserState_eval/cont:
EC_0 ARCH_0 |- parserStateIR : EC_2 ARCH_2 transitionResult
-- if annotationList STATE nameIR `{parserStatementListIR transitionStatementIR} = parserStateIR
-- ParserStmts_eval: EC_0 ARCH_0 |- parserStatementListIR : EC_1 ARCH_1 `EMPTY
-- ParserTransition_eval: EC_1 ARCH_1 |- transitionStatementIR : EC_2 ARCH_2 transitionResult
15.4. Parser statements
The syntax for parser statements is given by the following grammar rules:
parserStatement
: constantDeclaration
| variableDeclaration
| emptyStatement
| assignmentStatement
| callStatement
| directApplicationStatement
| parserBlockStatement
| parserConditionalStatement
;
parserBlockStatement
: annotationList `{ parserStatementList }
;
parserConditionalStatement
: IF `( expression ) parserStatement
| IF `( expression ) parserStatement ELSE parserStatement
;
parserStatementList
: /* empty */
| parserStatementList parserStatement
;
15.4.1. Semantics of parser statements
15.4.1.1. Type checking
Click to view the specification source
relation ParserStmt_ok: typingContext |- parserStatement : typingContext parserStatementIR
After type checking, parser statements are represented in P4IR as follows:
parserStatementIR
: constantDeclarationIR
| variableDeclarationIR
| emptyStatementIR
| assignmentStatementIR
| callStatementIR
| directApplicationStatementIR
| parserBlockStatementIR
| parserConditionalStatementIR
;
parserBlockStatementIR
: annotationList `{ parserStatementListIR }
;
parserConditionalStatementIR
: IF `( typedExpressionIR ) parserStatementIR
| IF `( typedExpressionIR ) parserStatementIR ELSE parserStatementIR
;
A list of parser statements is type checked by:
Click to view the specification source
relation ParserStmtList_ok: typingContext |- parserStatementList : typingContext parserStatementIR*
Click to view the specification source
rulegroup ParserStmtList_ok: rule ParserStmtList_ok: TC_0 |- parserStatementList : TC_1 parserStatementIR* -- if parserStatement* = $flatten_parserStatementList(parserStatementList) -- ParserStmts_ok: TC_0 |- parserStatement* : TC_1 parserStatementIR*
Click to view the specification source
relation ParserStmts_ok: typingContext |- parserStatement* : typingContext parserStatementIR*
Click to view the specification source
rulegroup ParserStmts_ok: rule ParserStmts_ok/nil: TC |- eps : TC eps rule ParserStmts_ok/cons: TC_0 |- parserStatement_h :: parserStatement_t* : TC_2 (parserStatementIR_h :: parserStatementIR_t*) -- ParserStmt_ok: TC_0 |- parserStatement_h : TC_1 parserStatementIR_h -- ParserStmts_ok: TC_1 |- parserStatement_t* : TC_2 parserStatementIR_t*
15.4.1.2. Compile-time evaluation
Click to view the specification source
relation ParserStmt_inst: instContext store |- parserStatementIR : instContext store parserStatementIR
A list of parser statements is compile-time evaluated by:
Click to view the specification source
relation ParserStmts_inst: instContext store |- parserStatementIR* : instContext store parserStatementIR*
Click to view the specification source
rulegroup ParserStmts_inst: rule ParserStmts_inst/nil: IC STO |- eps : IC STO eps rule ParserStmts_inst/cons: IC_0 STO_0 |- parserStatementIR_h :: parserStatementIR_t* : IC_2 STO_2 (parserStatementIR_h_inst :: parserStatementIR_t_inst*) -- ParserStmt_inst: IC_0 STO_0 |- parserStatementIR_h : IC_1 STO_1 parserStatementIR_h_inst -- ParserStmts_inst: IC_1 STO_1 |- parserStatementIR_t* : IC_2 STO_2 parserStatementIR_t_inst*
15.4.1.3. Runtime evaluation
Click to view the specification source
relation ParserStmt_eval: evalContext arch |- parserStatementIR : evalContext arch parserStatementResult
Evaluation of a parser statement yields:
parserStatementResult
: continueEmptyResult
| rejectTransitionResult
;
continueEmptyResult
: /* empty */
;
rejectTransitionResult
: REJECT errorValue
;
A list of parser statements is runtime evaluated by:
Click to view the specification source
relation ParserStmts_eval: evalContext arch |- parserStatementListIR : evalContext arch parserStatementResult
Click to view the specification source
rulegroup ParserStmts_eval: rule ParserStmts_eval/nil: EC ARCH |- eps : EC ARCH `EMPTY rule ParserStmts_eval/cons-head-reject: EC_0 ARCH_0 |- parserStatementIR_h :: parserStatementIR_t* : EC_1 ARCH_1 rejectTransitionResult -- ParserStmt_eval: EC_0 ARCH_0 |- parserStatementIR_h : EC_1 ARCH_1 rejectTransitionResult rule ParserStmts_eval/cons-head-cont: EC_0 ARCH_0 |- parserStatementIR_h :: parserStatementIR_t* : EC_2 ARCH_2 parserStatementResult -- ParserStmt_eval: EC_0 ARCH_0 |- parserStatementIR_h : EC_1 ARCH_1 `EMPTY -- ParserStmts_eval: EC_1 ARCH_1 |- parserStatementIR_t* : EC_2 ARCH_2 parserStatementResult
The subsequent sections describe each kind of statement in detail.
15.4.2. Constant declaration
See Section 11.3 for general information about constant declarations.
15.4.2.1. Type checking
Click to view the specification source
rulegroup ParserStmt_ok/constantDeclaration: rule ParserStmt_ok/constantDeclaration: TC_0 |- constantDeclaration : TC_1 constantDeclarationIR -- ConstDecl_ok: LOCAL TC_0 |- constantDeclaration : TC_1 constantDeclarationIR
15.4.2.2. Compile-time evaluation
Click to view the specification source
rulegroup ParserStmt_inst/constantDeclarationIR: rule ParserStmt_inst/constantDeclarationIR: IC_0 STO |- constantDeclarationIR : IC_1 STO constantDeclarationIR -- ConstDecl_inst: LOCAL IC_0 |- constantDeclarationIR : IC_1
15.4.2.3. Runtime evaluation
Click to view the specification source
rulegroup ParserStmt_eval/constantDeclarationIR: rule ParserStmt_eval/constantDeclarationIR: EC_0 ARCH |- constantDeclarationIR : EC_1 ARCH `EMPTY -- ConstDecl_eval: LOCAL EC_0 |- constantDeclarationIR : EC_1
15.4.3. Variable declaration
See Section 11.2 for general information about variable declarations.
15.4.3.1. Type checking
Click to view the specification source
rulegroup ParserStmt_ok/variableDeclaration: rule ParserStmt_ok/variableDeclaration: TC_0 |- variableDeclaration : TC_1 variableDeclarationIR -- VarDecl_ok: LOCAL TC_0 |- variableDeclaration : TC_1 variableDeclarationIR
15.4.3.2. Compile-time evaluation
Click to view the specification source
rulegroup ParserStmt_inst/variableDeclarationIR: rule ParserStmt_inst/variableDeclarationIR: IC STO |- variableDeclarationIR : IC STO variableDeclarationIR
15.4.3.3. Runtime evaluation
Click to view the specification source
rulegroup ParserStmt_eval/variableDeclarationIR: rule ParserStmt_eval/reject: EC_0 ARCH_0 |- variableDeclarationIR : EC_1 ARCH_1 rejectTransitionResult -- VarDecl_eval: LOCAL EC_0 ARCH_0 |- variableDeclarationIR : EC_1 ARCH_1 rejectTransitionResult rule ParserStmt_eval/cont: EC_0 ARCH_0 |- variableDeclarationIR : EC_1 ARCH_1 `EMPTY -- VarDecl_eval: LOCAL EC_0 ARCH_0 |- variableDeclarationIR : EC_1 ARCH_1 `EMPTY
15.4.4. Empty statement
See Section 13.2 for general information about empty statements.
15.4.4.1. Type checking
Click to view the specification source
rulegroup ParserStmt_ok/emptyStatement: rule ParserStmt_ok/emptyStatement: TC |- emptyStatement : TC emptyStatementIR -- Stmt_ok: LOCAL TC CONT NOLOOP |- emptyStatement : TC CONT emptyStatementIR
15.4.4.2. Compile-time evaluation
Click to view the specification source
rulegroup ParserStmt_inst/emptyStatementIR: rule ParserStmt_inst/emptyStatementIR: IC_0 STO_0 |- emptyStatementIR : IC_1 STO_1 emptyStatementIR_inst -- Stmt_inst: LOCAL IC_0 STO_0 |- emptyStatementIR : IC_1 STO_1 emptyStatementIR_inst
15.4.4.3. Runtime evaluation
Click to view the specification source
rulegroup ParserStmt_eval/emptyStatementIR: rule ParserStmt_eval/emptyStatementIR: EC_0 ARCH_0 |- emptyStatementIR : EC_1 ARCH_1 `EMPTY -- Stmt_eval: LOCAL EC_0 ARCH_0 |- emptyStatementIR : EC_1 ARCH_1 `EMPTY
15.4.5. Assignment statement
See Section 13.3 for general information about assignment statements.
15.4.5.1. Type checking
Click to view the specification source
rulegroup ParserStmt_ok/assignmentStatement: rule ParserStmt_ok/assignmentStatement: TC_0 |- assignmentStatement : TC_1 assignmentStatementIR -- Stmt_ok: LOCAL TC_0 CONT NOLOOP |- assignmentStatement : TC_1 CONT assignmentStatementIR
15.4.5.2. Compile-time evaluation
Click to view the specification source
rulegroup ParserStmt_inst/assignmentStatementIR: rule ParserStmt_inst/assignmentStatementIR: IC_0 STO_0 |- assignmentStatementIR : IC_1 STO_1 assignmentStatementIR_inst -- Stmt_inst: LOCAL IC_0 STO_0 |- assignmentStatementIR : IC_1 STO_1 assignmentStatementIR_inst
15.4.5.3. Runtime evaluation
Click to view the specification source
rulegroup ParserStmt_eval/assignmentStatementIR: rule ParserStmt_eval/reject: EC_0 ARCH_0 |- assignmentStatementIR : EC_1 ARCH_1 rejectTransitionResult -- Stmt_eval: LOCAL EC_0 ARCH_0 |- assignmentStatementIR : EC_1 ARCH_1 rejectTransitionResult rule ParserStmt_eval/cont: EC_0 ARCH_0 |- assignmentStatementIR : EC_1 ARCH_1 `EMPTY -- Stmt_eval: LOCAL EC_0 ARCH_0 |- assignmentStatementIR : EC_1 ARCH_1 `EMPTY
15.4.6. Call statement
See Section 13.4 for general information about call statements.
15.4.6.1. Type checking
Click to view the specification source
rulegroup ParserStmt_ok/callStatement: rule ParserStmt_ok/callStatement: TC_0 |- callStatement : TC_1 callStatementIR -- Stmt_ok: LOCAL TC_0 CONT NOLOOP |- callStatement : TC_1 CONT callStatementIR
15.4.6.2. Compile-time evaluation
Click to view the specification source
rulegroup ParserStmt_inst/callStatementIR: rule ParserStmt_inst/callStatementIR: IC_0 STO_0 |- callStatementIR : IC_1 STO_1 callStatementIR_inst -- Stmt_inst: LOCAL IC_0 STO_0 |- callStatementIR : IC_1 STO_1 callStatementIR_inst
15.4.6.3. Runtime evaluation
Click to view the specification source
rulegroup ParserStmt_eval/callStatementIR: rule ParserStmt_eval/reject: EC_0 ARCH_0 |- callStatementIR : EC_1 ARCH_1 rejectTransitionResult -- Stmt_eval: LOCAL EC_0 ARCH_0 |- callStatementIR : EC_1 ARCH_1 rejectTransitionResult rule ParserStmt_eval/cont: EC_0 ARCH_0 |- callStatementIR : EC_1 ARCH_1 `EMPTY -- Stmt_eval: LOCAL EC_0 ARCH_0 |- callStatementIR : EC_1 ARCH_1 `EMPTY
15.4.7. Direct type invocation
See Section 13.5 for general information about direct type invocations.
15.4.7.1. Type checking
Click to view the specification source
rulegroup ParserStmt_ok/directApplicationStatement: rule ParserStmt_ok/directApplicationStatement: TC_0 |- directApplicationStatement : TC_1 directApplicationStatementIR -- Stmt_ok: LOCAL TC_0 CONT NOLOOP |- directApplicationStatement : TC_1 CONT directApplicationStatementIR
15.4.7.2. Compile-time evaluation
Click to view the specification source
rulegroup ParserStmt_inst/directApplicationStatementIR:
rule ParserStmt_inst/directApplicationStatementIR:
IC STO_0 |- directApplicationStatementIR : IC STO_1 parserBlockStatementIR
-- DirectApplicationStmt_inst: LOCAL IC STO_0 |- directApplicationStatementIR : STO_1 constantDeclarationIR callStatementIR
-- if parserBlockStatementIR = `EMPTY `{[constantDeclarationIR, callStatementIR]}
15.4.8. Block statement
A parser block statement is a sequence of parser statements enclosed in braces. It is similar to a block statement explained in Section 13.8, but it can only contain parser statements.
parserBlockStatement
: annotationList `{ parserStatementList }
;
15.4.8.1. Type checking
After type checking, a parser block statement is represented in P4IR as:
parserBlockStatementIR
: annotationList `{ parserStatementListIR }
;
Click to view the specification source
rulegroup ParserStmt_ok/parserBlockStatement:
rule ParserStmt_ok/parserBlockStatement:
TC_0 |- annotationList `{parserStatementList} : TC_1 parserBlockStatementIR
-- if TC_1 = $enter_t(TC_0)
-- ParserStmtList_ok: TC_1 |- parserStatementList : TC_2 parserStatementIR*
-- if TC_3 = $exit_t(TC_2)
-- if parserBlockStatementIR = annotationList `{parserStatementIR*}
15.4.8.2. Compile-time evaluation
Click to view the specification source
rulegroup ParserStmt_inst/parserBlockStatementIR:
rule ParserStmt_inst/parserBlockStatementIR:
IC_0 STO_0 |- annotationList `{parserStatementListIR} : IC_1 STO_1 parserBlockStatementIR_inst
-- ParserStmts_inst: IC_0 STO_0 |- parserStatementListIR : IC_1 STO_1 parserStatementListIR_inst
-- if parserBlockStatementIR_inst = annotationList `{parserStatementListIR_inst}
15.4.8.3. Runtime evaluation
Click to view the specification source
rulegroup ParserStmt_eval/parserBlockStatementIR:
rule ParserStmt_eval/parserBlockStatementIR:
EC_0 ARCH_0 |- annotationList `{parserStatementListIR} : EC_3 ARCH_1 parserStatementResult
-- if EC_1 = $enter_e(EC_0)
-- ParserStmts_eval: EC_1 ARCH_0 |- parserStatementListIR : EC_2 ARCH_1 parserStatementResult
-- if EC_3 = $exit_e(EC_2)
15.4.9. Conditional statement
A parser conditional statement is defined as:
parserConditionalStatement
: IF `( expression ) parserStatement
| IF `( expression ) parserStatement ELSE parserStatement
;
It is similar to a conditional statement explained in Section 13.9, but it can only contain parser statements in its branches.
15.4.9.1. Type checking
After type checking, a parser conditional statement is represented in P4IR as:
parserConditionalStatementIR
: IF `( typedExpressionIR ) parserStatementIR
| IF `( typedExpressionIR ) parserStatementIR ELSE parserStatementIR
;
Click to view the specification source
rulegroup ParserStmt_ok/parserConditionalStatement: rule ParserStmt_ok/non-else: TC |- IF `(expression_cond) parserStatement_then : TC (IF `(typedExpressionIR_cond) parserStatementIR_then) -- Expr_ok: LOCAL TC |- expression_cond : typedExpressionIR_cond -- if typeIR_cond = $type_of_typedExpressionIR(typedExpressionIR_cond) -- if BOOL = $unroll_typeIR(typeIR_cond) -- ParserStmt_ok: TC |- parserStatement_then : TC_then parserStatementIR_then rule ParserStmt_ok/else: TC |- IF `(expression_cond) parserStatement_then ELSE parserStatement_else : TC (IF `(typedExpressionIR_cond) parserStatementIR_then ELSE parserStatementIR_else) -- Expr_ok: LOCAL TC |- expression_cond : typedExpressionIR_cond -- if typeIR_cond = $type_of_typedExpressionIR(typedExpressionIR_cond) -- if BOOL = $unroll_typeIR(typeIR_cond) -- ParserStmt_ok: TC |- parserStatement_then : TC_then parserStatementIR_then -- ParserStmt_ok: TC |- parserStatement_else : TC_else parserStatementIR_else
15.4.9.2. Compile-time evaluation
Click to view the specification source
rulegroup ParserStmt_inst/parserConditionalStatementIR: rule ParserStmt_inst/non-else: IC STO_0 |- IF `(typedExpressionIR_cond) parserStatementIR_then : IC STO_1 (IF `(typedExpressionIR_cond) parserStatementIR_then_inst) -- ParserStmt_inst: IC STO_0 |- parserStatementIR_then : IC_then STO_1 parserStatementIR_then_inst rule ParserStmt_inst/else: IC STO_0 |- IF `(typedExpressionIR_cond) parserStatementIR_then ELSE parserStatementIR_else : IC STO_2 (IF `(typedExpressionIR_cond) parserStatementIR_then_inst ELSE parserStatementIR_else_inst) -- ParserStmt_inst: IC STO_0 |- parserStatementIR_then : IC_then STO_1 parserStatementIR_then_inst -- ParserStmt_inst: IC STO_1 |- parserStatementIR_else : IC_else STO_2 parserStatementIR_else_inst
15.4.9.3. Runtime evaluation
Click to view the specification source
rulegroup ParserStmt_eval/parserConditionalStatementIR: rule ParserStmt_eval/non-else-reject: EC ARCH |- IF `(typedExpressionIR_cond) parserStatementIR_then : EC_cond ARCH_cond rejectTransitionResult -- Expr_eval: LOCAL EC ARCH |- typedExpressionIR_cond : EC_cond ARCH_cond rejectTransitionResult rule ParserStmt_eval/non-else-cont-true: EC ARCH |- IF `(typedExpressionIR_cond) parserStatementIR_then : EC_then ARCH_then parserStatementResult -- Expr_eval: LOCAL EC ARCH |- typedExpressionIR_cond : EC_cond ARCH_cond (` (`B true)) -- ParserStmt_eval: EC_cond ARCH_cond |- parserStatementIR_then : EC_then ARCH_then parserStatementResult rule ParserStmt_eval/non-else-cont-false: EC ARCH |- IF `(typedExpressionIR_cond) parserStatementIR_then : EC_cond ARCH_cond `EMPTY -- Expr_eval: LOCAL EC ARCH |- typedExpressionIR_cond : EC_cond ARCH_cond (` (`B false)) rule ParserStmt_eval/else-cont-true: EC ARCH |- IF `(typedExpressionIR_cond) parserStatementIR_then ELSE parserStatementIR_else : EC_then ARCH_then parserStatementResult -- Expr_eval: LOCAL EC ARCH |- typedExpressionIR_cond : EC_cond ARCH_cond (` (`B true)) -- ParserStmt_eval: EC_cond ARCH_cond |- parserStatementIR_then : EC_then ARCH_then parserStatementResult rule ParserStmt_eval/else-cont-false: EC ARCH |- IF `(typedExpressionIR_cond) parserStatementIR_then ELSE parserStatementIR_else : EC_else ARCH_else parserStatementResult -- Expr_eval: LOCAL EC ARCH |- typedExpressionIR_cond : EC_cond ARCH_cond (` (`B false)) -- ParserStmt_eval: EC_cond ARCH_cond |- parserStatementIR_else : EC_else ARCH_else parserStatementResult
15.5. Parser transition statements
The last statement in a parser state is an optional transition statement,
which transfers control to another state, possibly accept or reject. A
transition statements is written using the following syntax:
transitionStatement
: /* empty */
| TRANSITION stateExpression
;
stateExpression
: name ;
| selectExpression
;
The execution of the transition statement causes stateExpression to be
evaluated, and transfers control to the resulting state.
In terms of the ParserModel, the semantics of a transition statement can be
formalized as follows:
goto eval(stateExpression)
For example, this statement:
transition accept;
terminates execution of the current parser and transitions immediately to
the accept state.
If the body of a state block does not end with a transition statement, the
implied statement is:
transition reject;
See Section 15.6 for description of the select expression that
can be used as the stateExpression in a transition statement.
15.5.1. Type checking
Parser transition statements are type-checked as follows:
Click to view the specification source
relation ParserTransition_ok: typingContext nameIR* |- transitionStatement : transitionStatementIR
Click to view the specification source
rulegroup ParserTransition_ok:
rule ParserTransition_ok/empty:
TC_0 nameIR_state* |- `EMPTY : TRANSITION ("reject" ;)
rule ParserTransition_ok/name:
TC_0 nameIR_state* |- TRANSITION (name ;) : transitionStatementIR
-- if nameIR = $name(name)
-- if nameIR <- nameIR_state*
-- if transitionStatementIR = TRANSITION (nameIR ;)
rule ParserTransition_ok/select:
TC_0 nameIR_state* |- TRANSITION selectExpression : TRANSITION selectExpressionIR
-- ParserSelect_ok: TC_0 nameIR_state* |- selectExpression : selectExpressionIR
After type checking, transition statements are represented in P4IR as:
transitionStatementIR
: TRANSITION stateExpressionIR
;
stateExpressionIR
: nameIR ;
| selectExpressionIR
;
15.5.2. Runtime evaluation
Click to view the specification source
relation ParserTransition_eval: evalContext arch |- transitionStatementIR : evalContext arch transitionResult
Click to view the specification source
rulegroup ParserTransition_eval: rule ParserTransition_eval/nameIR-accept: EC ARCH |- TRANSITION (nameIR ;) : EC ARCH ACCEPT -- if nameIR = "accept" rule ParserTransition_eval/nameIR-reject: EC ARCH |- TRANSITION (nameIR ;) : EC ARCH (REJECT errorValue) -- if nameIR = "reject" -- if errorValue = ERROR . "NoError" rule ParserTransition_eval/nameIR-state: EC ARCH |- TRANSITION (nameIR ;) : EC ARCH (STATE nameIR) -- if nameIR =/= "accept" /\ nameIR =/= "reject" rule ParserTransition_eval/selectExpressionIR: EC_0 ARCH_0 |- TRANSITION selectExpressionIR : EC_1 ARCH_1 transitionResult -- ParserSelect_eval: EC_0 ARCH_0 |- selectExpressionIR : EC_1 ARCH_1 transitionResult
15.6. Parser select expression
A select expression evaluates to a state. The syntax for a select
expression is as follows:
selectExpression
: SELECT `( expressionList ) `{ selectCaseList }
;
selectCaseList
: /* empty */
| selectCaseList selectCase
;
selectCase
: keysetExpression : name ;
;
Each expression in the expressionList must have a set type, or a type that
can be nested in a set type.
In terms of the ParserModel, the meaning of a select expression:
select(e) {
ks[0]: s[0];
ks[1]: s[1];
/* more labels omitted */
ks[n-2]: s[n-1];
_ : sd; // ks[n-1] is default
}
is defined in pseudo-code as:
key = eval(e);
for (int i=0; i < n; i++) {
keyset = eval(ks[i]);
if (keyset.contains(key)) return s[i];
}
verify(false, error.NoMatch);
Some targets may require that all keyset expressions in a select expression be
compile-time known values. Keysets are evaluated in order, from top to bottom
as implied by the pseudo-code above; the first keyset that includes the value
in the select argument provides the result state. If no label matches, the
execution triggers a runtime error with the standard error code
error.NoMatch.
Note that this implies that all cases after a default or _ label are
unreachable; the compiler should emit a warning if it detects unreachable
cases. This constitutes an important difference between select expressions
and the switch statements found in many programming languages since the
keysets of a select expression may "overlap".
The typical way to use a select expression is to compare the value of a
recently-extracted header field against a set of values, as in the following
example:
header IPv4_h { bit<8> protocol; /* more fields omitted */ }
struct P { IPv4_h ipv4; /* more fields omitted */ }
P headers;
select (headers.ipv4.protocol) {
8w6 : parse_tcp;
8w17 : parse_udp;
_ : accept;
}
For example, to detect TCP reserved ports (< 1024) one could write:
select (p.tcp.port) {
16w0 &&& 16w0xFC00: well_known_port;
_: other_port;
}
The expression 16w0 &&& 16w0xFC00 describes the set of 16-bit values whose
most significant six bits are zero.
Some targets may support parser value sets; see Section 10.7. Given
a type T for the type parameter of the value set, the type of the value set
is set<T>. The type of the value set must match to the type of all other
keysetExpressions in the same select expression. If there is a mismatch,
the compiler must raise an error. The type of the values in the set must be
either bit<>, int<>, tuple, struct, or serializable enum.
For example, to allow the control plane API to specify TCP reserved ports at runtime, one could write:
struct vsk_t {
@match(ternary)
bit<16> port;
}
value_set<vsk_t>(4) pvs;
select (p.tcp.port) {
pvs: runtime_defined_port;
_: other_port;
}
The above example allows the runtime API to populate up to 4 different
keysetExpressions in the value_set. If the value_set takes a struct as
type parameter, the runtime API can use the struct field names to name the
objects in the value set. The match type of the struct field is specified with
the @match annotation. If the @match annotation is not specified on a
struct field, by default it is assumed to be @match(exact). A single
non-exact field must be placed into a struct by itself, with the desired
@match annotation.
15.6.1. Type checking
A select expression is type checked as follows:
Click to view the specification source
relation ParserSelect_ok: typingContext nameIR* |- selectExpression : selectExpressionIR
Click to view the specification source
rulegroup ParserSelect_ok:
rule ParserSelect_ok:
TC_0 nameIR_state* |- SELECT `(expressionList_key) `{selectCaseList} : selectExpressionIR
-- if expression_key* = $flatten_expressionList(expressionList_key)
-- (Expr_ok: LOCAL TC_0 |- expression_key : typedExpressionIR_key)*
-- (if (typedExpressionIR_key_reduced = $reduce_serenum(typedExpressionIR_key)))*
-- (if (typeIR_key_reduced = $type_of_typedExpressionIR(typedExpressionIR_key_reduced)))*
-- (Type_wf: $bound(LOCAL, TC_0) |- SET `<typeIR_key_reduced>)*
-- if selectCase* = $flatten_selectCaseList(selectCaseList)
-- (SelectCase_ok: TC_0 nameIR_state* typeIR_key_reduced* |- selectCase : selectCaseIR)*
-- if selectExpressionIR = SELECT `(typedExpressionIR_key_reduced*) `{selectCaseIR*}
The type checking of a select case visits its keyset expressions, and each simple keyset expression is visited in turn.
Click to view the specification source
relation SelectCase_ok: typingContext nameIR* typeIR* |- selectCase : selectCaseIR
Click to view the specification source
rulegroup SelectCase_ok: rule SelectCase_ok: TC nameIR_state* typeIR_key* |- keysetExpression : name ; : keysetExpressionIR : nameIR ; -- SelectCase_keyset_ok: TC typeIR_key* |- keysetExpression : keysetExpressionIR -- if nameIR = $name(name) -- if nameIR <- nameIR_state*
A keyset expression is type checked with the relation:
keysetExpression
: simpleKeysetExpression
| tupleKeysetExpression
;
Click to view the specification source
relation SelectCase_keyset_ok: typingContext typeIR* |- keysetExpression : keysetExpressionIR
A simple keyset expression is type checked as follows:
simpleKeysetExpression
: expression
| expression &&& expression
| expression .. expression
| DEFAULT
| _
;
Click to view the specification source
relation SelectCase_keyset_simple_ok: typingContext typeIR |- simpleKeysetExpression : simpleKeysetExpressionIR
Expression keys
Click to view the specification source
rulegroup SelectCase_keyset_simple_ok/expression: rule SelectCase_keyset_simple_ok/set-alpha: TC typeIR_key |- expression : typedExpressionIR -- Expr_ok: LOCAL TC |- expression : typedExpressionIR -- if typeIR = $type_of_typedExpressionIR(typedExpressionIR) -- if SET `<typeIR_base> = $unroll_typeIR(typeIR) -- Type_alpha: typeIR_base ~~ typeIR_key rule SelectCase_keyset_simple_ok/set-subtype: TC typeIR_key |- expression : typedExpressionIR_cast -- Expr_ok: LOCAL TC |- expression : typedExpressionIR -- if typeIR = $type_of_typedExpressionIR(typedExpressionIR) -- if SET `<typeIR_base> = $unroll_typeIR(typeIR) -- Type_alpha:/ typeIR_base ~~ typeIR_key -- Cast_impl: typeIR_base -> typeIR_key -- if typeIR_cast = SET `<typeIR_key> -- if typedExpressionIR_cast = (`(typeIR_cast) typedExpressionIR) # `(typeIR_cast DYN) rule SelectCase_keyset_simple_ok/non-set: TC typeIR_key |- expression : typedExpressionIR_cast -- Expr_ok: LOCAL TC |- expression : typedExpressionIR -- if typeIR = $type_of_typedExpressionIR(typedExpressionIR) -- if typeIR_unroll = $unroll_typeIR(typeIR) -- if ~(typeIR_unroll <: setTypeIR) -- Cast_impl: typeIR -> typeIR_key -- if typeIR_cast = SET `<typeIR_key> -- if typedExpressionIR_cast = (`(typeIR_cast) typedExpressionIR) # `(typeIR_cast DYN)
Mask keys
Click to view the specification source
rulegroup SelectCase_keyset_simple_ok/mask: rule SelectCase_keyset_simple_ok/mask: TC typeIR_key |- expression_l &&& expression_r : typedExpressionIR_l_casted &&& typedExpressionIR_r_casted -- Expr_ok: LOCAL TC |- expression_l : typedExpressionIR_l -- Expr_ok: LOCAL TC |- expression_r : typedExpressionIR_r -- if (typedExpressionIR_l_cast, typedExpressionIR_r_cast) = $cast_binary(typedExpressionIR_l, typedExpressionIR_r) -- if (typedExpressionIR_l_reduced, typedExpressionIR_r_reduced) = $reduce_serenum_binary(typedExpressionIR_l_cast, typedExpressionIR_r_cast, $compat_mask) -- if typeIR_reduced = $type_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if typedExpressionIR_l_casted = $cast_unary(typedExpressionIR_l_reduced, typeIR_key) -- if typedExpressionIR_r_casted = $cast_unary(typedExpressionIR_r_reduced, typeIR_key)
Range keys
Click to view the specification source
rulegroup SelectCase_keyset_simple_ok/range: rule SelectCase_keyset_simple_ok/range: TC typeIR_key |- expression_l .. expression_r : typedExpressionIR_l_casted .. typedExpressionIR_r_casted -- Expr_ok: LOCAL TC |- expression_l : typedExpressionIR_l -- Expr_ok: LOCAL TC |- expression_r : typedExpressionIR_r -- if (typedExpressionIR_l_cast, typedExpressionIR_r_cast) = $cast_binary(typedExpressionIR_l, typedExpressionIR_r) -- if (typedExpressionIR_l_reduced, typedExpressionIR_r_reduced) = $reduce_serenum_binary(typedExpressionIR_l_cast, typedExpressionIR_r_cast, $compat_range) -- if typeIR_reduced = $type_of_typedExpressionIR(typedExpressionIR_l_reduced) -- if typeIR_reduced = $type_of_typedExpressionIR(typedExpressionIR_r_reduced) -- if typedExpressionIR_l_casted = $cast_unary(typedExpressionIR_l_reduced, typeIR_key) -- if typedExpressionIR_r_casted = $cast_unary(typedExpressionIR_r_reduced, typeIR_key)
Default and don’t care keys
Click to view the specification source
rulegroup SelectCase_keyset_simple_ok/default: rule SelectCase_keyset_simple_ok/default: TC typeIR_key |- DEFAULT : DEFAULT
Click to view the specification source
rulegroup SelectCase_keyset_simple_ok/dontcare: rule SelectCase_keyset_simple_ok/dontcare: TC typeIR_key |- _ : _
15.6.2. Runtime evaluation
The runtime evaluation of a select expression is defined as follows:
Click to view the specification source
relation ParserSelect_eval: evalContext arch |- selectExpressionIR : evalContext arch transitionResult
Click to view the specification source
rulegroup ParserSelect_eval:
rule ParserSelect_eval/reject:
EC_0 ARCH_0 |- selectExpressionIR : EC_1 ARCH_1 rejectTransitionResult
-- if SELECT `(typedExpressionIR_key*) `{selectCaseListIR} = selectExpressionIR
-- Exprs_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR_key* : EC_1 ARCH_1 rejectTransitionResult
rule ParserSelect_eval/cont-selectCaseListIR-reject:
EC_0 ARCH_0 |- selectExpressionIR : EC_2 ARCH_2 rejectTransitionResult
-- if SELECT `(typedExpressionIR_key*) `{selectCaseListIR} = selectExpressionIR
-- Exprs_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR_key* : EC_1 ARCH_1 (` value_key*)
-- SelectCases_match: EC_1 ARCH_1 value_key* |- selectCaseListIR : EC_2 ARCH_2 rejectTransitionResult
rule ParserSelect_eval/no-match:
EC_0 ARCH_0 |- selectExpressionIR : EC_2 ARCH_2 rejectTransitionResult
-- if SELECT `(typedExpressionIR_key*) `{selectCaseListIR} = selectExpressionIR
-- Exprs_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR_key* : EC_1 ARCH_1 (` value_key*)
-- SelectCases_match: EC_1 ARCH_1 value_key* |- selectCaseListIR : EC_2 ARCH_2 (` nameIR_state?)
-- if eps = nameIR_state?
-- if rejectTransitionResult = REJECT (ERROR . "NoMatch")
rule ParserSelect_eval/match:
EC_0 ARCH_0 |- selectExpressionIR : EC_3 ARCH_3 transitionResult
-- if SELECT `(typedExpressionIR_key*) `{selectCaseListIR} = selectExpressionIR
-- Exprs_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR_key* : EC_1 ARCH_1 (` value_key*)
-- SelectCases_match: EC_1 ARCH_1 value_key* |- selectCaseListIR : EC_2 ARCH_2 (` nameIR_state)
-- ParserTransition_eval: EC_2 ARCH_2 |- TRANSITION (nameIR_state ;) : EC_3 ARCH_3 transitionResult
Matching against select cases is defined as follows:
Click to view the specification source
relation SelectCases_match: evalContext arch value* |- selectCaseListIR : evalContext arch selectCaseMatchResult
The result of matching a select case is defined as follows:
selectCaseMatchResult
: continueResult<nameIR?>
| rejectTransitionResult
;
Click to view the specification source
rulegroup SelectCases_match: rule SelectCases_match/nil: EC ARCH value_key* |- eps : EC ARCH (` eps) rule SelectCases_match/cons-head-match: EC_0 ARCH_0 value_key* |- selectCaseIR_h :: selectCaseIR_t* : EC_1 ARCH_1 rejectTransitionResult -- SelectCase_match: EC_0 ARCH_0 value_key* |- selectCaseIR_h : EC_1 ARCH_1 rejectTransitionResult rule SelectCases_match/cons-head-match: EC_0 ARCH_0 value_key* |- selectCaseIR_h :: selectCaseIR_t* : EC_1 ARCH_1 (` nameIR_state) -- SelectCase_match: EC_0 ARCH_0 value_key* |- selectCaseIR_h : EC_1 ARCH_1 (` nameIR_state) rule SelectCases_match/cons-head-no-match: EC_0 ARCH_0 value_key* |- selectCaseIR_h :: selectCaseIR_t* : EC_2 ARCH_2 selectCaseMatchResult -- SelectCase_match: EC_0 ARCH_0 value_key* |- selectCaseIR_h : EC_1 ARCH_1 (` eps) -- SelectCases_match: EC_1 ARCH_1 value_key* |- selectCaseIR_t* : EC_2 ARCH_2 selectCaseMatchResult
The matching of a select case is defined as follows:
Click to view the specification source
relation SelectCase_match: evalContext arch value* |- selectCaseIR : evalContext arch selectCaseMatchResult
Click to view the specification source
rulegroup SelectCase_match: rule SelectCase_match/reject: EC_0 ARCH_0 value_key* |- keysetExpressionIR : nameIR ; : EC_1 ARCH_1 rejectTransitionResult -- Expr_eval_keyset: EC_0 ARCH_0 |- keysetExpressionIR : EC_1 ARCH_1 rejectTransitionResult rule SelectCase_match/no-match: EC_0 ARCH_0 value_key* |- keysetExpressionIR : nameIR ; : EC_1 ARCH_1 (` eps) -- Expr_eval_keyset: EC_0 ARCH_0 |- keysetExpressionIR : EC_1 ARCH_1 (` setValue*) -- if ~$match_keysets(setValue*, value_key*) rule SelectCase_match/match: EC_0 ARCH_0 value_key* |- keysetExpressionIR : nameIR ; : EC_1 ARCH_1 (` nameIR) -- Expr_eval_keyset: EC_0 ARCH_0 |- keysetExpressionIR : EC_1 ARCH_1 (` setValue*) -- if $match_keysets(setValue*, value_key*)
15.7. verify
The verify statement provides a simple form of error
handling. verify can only be invoked within a parser; it is
used syntactically as if it were a function with the following
signature:
extern void verify(in bool condition, in error err);
If the first argument is true, then executing the statement has no
side-effect. However, if the first argument is false, it causes an
immediate transition to reject, which causes immediate parsing termination;
at the same time, the parserError associated with the parser is set to the
value of the second argument.
In terms of the ParserModel the semantics of a verify
statement is given by:
ParserModel.verify(bool condition, error err) {
if (condition == false) {
ParserModel.parserError = err;
goto reject;
}
}
15.8. Packet data extraction
The P4 core library contains the following declaration of a built-in extern
type called packet_in that represents incoming network packets. The
packet_in extern is special: it cannot be instantiated by the user
explicitly. Instead, the architecture supplies a separate instance for each
packet_in argument to a parser instantiation.
extern packet_in {
void extract<T>(out T headerLvalue);
void extract<T>(out T variableSizeHeader, in bit<32> varFieldSizeBits);
T lookahead<T>();
bit<32> length(); // This method may be unavailable in some architectures
void advance(bit<32> bits);
}
To extract data from a packet represented by an argument b with type
packet_in, a parser invokes the extract methods of b. There are two
variants of the extract method: a one-argument variant for extracting
fixed-size headers, and a two-argument variant for extracting variable-sized
headers. Because these operations can cause runtime verification failures (see
below), these methods can only be executed within parsers.
When extracting data into a bit-string or integer, the first packet bit is extracted to the most significant bit of the integer.
Some targets may perform cut-through packet processing, i.e., they may start
processing a packet before its length is known (i.e., before all bytes have
been received). On such a target calls to the packet_in.length() method
cannot be implemented. Attempts to call this method should be flagged as errors
(either at compilation time by the compiler back-end, or when attempting to
load the compiled P4 program onto a target that does not support this method).
In terms of the ParserModel, the semantics of packet_in can be captured
using the following abstract model of packets:
packet_in {
unsigned nextBitIndex;
byte[] data;
unsigned lengthInBits;
void initialize(byte[] data) {
this.data = data;
this.nextBitIndex = 0;
this.lengthInBits = data.sizeInBytes * 8;
}
bit<32> length() { return this.lengthInBits / 8; }
}
15.8.1. Fixed-width extraction
The single-argument extract method handles fixed-width headers, and is
declared in P4 as follows:
void extract<T>(out T headerLvalue);
The expression headerLvalue must evaluate to an l-value (see
Chapter 12) of type header with a fixed width. If this method executes
successfully, on completion the headerLvalue is filled with data from the
packet and its validity bit is set to true. This method may fail in various
ways—e.g., if there are not enough bits left in the packet to fill the
specified header.
For example, the following program fragment extracts an Ethernet header:
struct Result { Ethernet_h ethernet; /* more fields omitted */ }
parser P(packet_in b, out Result r) {
state start {
b.extract(r.ethernet);
}
}
In terms of the ParserModel, the semantics of the single-argument extract
is given in terms of the following pseudo-code method, using data from the
packet class defined above. We use the special valid$ identifier to
indicate the hidden valid bit of a header, isNext$ to indicate that the
l-value was obtained using next, and nextIndex$ to indicate the
corresponding header or header union stack properties.
void packet_in.extract<T>(out T headerLValue) {
bitsToExtract = sizeofInBits(headerLValue);
lastBitNeeded = this.nextBitIndex + bitsToExtract;
ParserModel.verify(this.lengthInBits >= lastBitNeeded, error.PacketTooShort);
headerLValue = this.data.extractBits(this.nextBitIndex, bitsToExtract);
headerLValue.valid$ = true;
if headerLValue.isNext$ {
verify(headerLValue.nextIndex$ < headerLValue.size, error.StackOutOfBounds);
headerLValue.nextIndex$ = headerLValue.nextIndex$ + 1;
}
this.nextBitIndex += bitsToExtract;
}
15.8.2. Variable-width extraction
The two-argument extract handles variable-width headers, and is declared in
P4 as follows:
void extract<T>(out T headerLvalue, in bit<32> variableFieldSize);
The expression headerLvalue must be an l-value representing a header that
contains exactly one varbit field. The expression variableFieldSize must
evaluate to a bit<32> value that indicates the number of bits to be extracted
into the unique varbit field of the header (i.e., this size is not the size
of the complete header, just the varbit field).
In terms of the ParserModel, the semantics of the two-argument extract is
captured by the following pseudo-code:
void packet_in.extract<T>(out T headerLvalue,
in bit<32> variableFieldSize) {
// targets are allowed to include the following line, but need not
// verify(variableFieldSize[2:0] == 0, error.ParserInvalidArgument);
bitsToExtract = sizeOfFixedPart(headerLvalue) + variableFieldSize;
lastBitNeeded = this.nextBitIndex + bitsToExtract;
ParserModel.verify(this.lengthInBits >= lastBitNeeded, error.PacketTooShort);
ParserModel.verify(bitsToExtract <= headerLvalue.maxSize, error.HeaderTooShort);
headerLvalue = this.data.extractBits(this.nextBitIndex, bitsToExtract);
headerLvalue.varbitField.size = variableFieldSize;
headerLvalue.valid$ = true;
if headerLValue.isNext$ {
verify(headerLValue.nextIndex$ < headerLValue.size, error.StackOutOfBounds);
headerLValue.nextIndex$ = headerLValue.nextIndex$ + 1;
}
this.nextBitIndex += bitsToExtract;
}
The following example shows one way to parse IPv4 options—by splitting the IPv4 header into two separate headers:
// IPv4 header without options
header IPv4_no_options_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;
}
header IPv4_options_h {
varbit<320> options;
}
struct Parsed_headers {
// Some fields omitted
IPv4_no_options_h ipv4;
IPv4_options_h ipv4options;
}
error { InvalidIPv4Header }
parser Top(packet_in b, out Parsed_headers headers) {
// Some states omitted
state parse_ipv4 {
b.extract(headers.ipv4);
verify(headers.ipv4.ihl >= 5, error.InvalidIPv4Header);
transition select (headers.ipv4.ihl) {
5: dispatch_on_protocol;
_: parse_ipv4_options;
}
}
state parse_ipv4_options {
// use information in the ipv4 header to compute the number of bits to extract
b.extract(headers.ipv4options,
(bit<32>)(((bit<16>)headers.ipv4.ihl - 5) * 32));
transition dispatch_on_protocol;
}
}
15.8.3. Lookahead
The lookahead method provided by the packet_in packet abstraction evaluates
to a set of bits from the input packet without advancing the nextBitIndex
pointer. Similar to extract, it will transition to reject and set the error
if there are not enough bits in the packet. When lookahead returns a value
that contains headers (e.g., a header type, or a struct containing headers),
the headers values in the returned result are always valid (otherwise
lookahead must have transitioned to the reject state).
The lookahead method can be invoked as follows:
b.lookahead<T>()
where T must be a type with fixed width. In case of success the result of the
evaluation of lookahead returns a value of type T.
In terms of the ParserModel, the semantics of lookahead is given by the
following pseudocode:
T packet_in.lookahead<T>() {
bitsToExtract = sizeof(T);
lastBitNeeded = this.nextBitIndex + bitsToExtract;
ParserModel.verify(this.lengthInBits >= lastBitNeeded, error.PacketTooShort);
T tmp = this.data.extractBits(this.nextBitIndex, bitsToExtract);
return tmp;
}
The TCP options example from Section 8.4.7 also illustrates how
lookahead can be used:
state start {
transition select(b.lookahead<bit<8>>()) {
0: parse_tcp_option_end;
1: parse_tcp_option_nop;
2: parse_tcp_option_ss;
3: parse_tcp_option_s;
5: parse_tcp_option_sack;
}
}
// Some states omitted
state parse_tcp_option_sack {
bit<8> n = b.lookahead<Tcp_option_sack_top>().length;
b.extract(vec.next.sack, (bit<32>) (8 * n - 16));
transition start;
}
15.8.4. Skipping bits
P4 provides two ways to skip over bits in an input packet without assigning them to a header:
One way is to extract to the underscore identifier, explicitly specifying the
type of the data:
b.extract<T>(_)
Another way is to use the advance method of the packet when the number of
bits to skip is known.
In terms of the ParserModel, the meaning of advance is given in pseudocode
as follows:
void packet_in.advance(bit<32> bits) {
// targets are allowed to include the following line, but need not
// verify(bits[2:0] == 0, error.ParserInvalidArgument);
lastBitNeeded = this.nextBitIndex + bits;
ParserModel.verify(this.lengthInBits >= lastBitNeeded, error.PacketTooShort);
this.nextBitIndex += bits;
}
16. Abstract control machine
A control declaration introduces a constructor for a control object. See Section 10.5 and Section 11.12 for details.
controlDeclaration
: annotationList CONTROL name typeParameterListOpt `( parameterList )
constructorParameterListOpt
`{ controlLocalDeclarationList APPLY controlBody }
;
controlLocalDeclaration
: constantDeclaration
| instantiation
| variableDeclaration
| actionDeclaration
| tableDeclaration
;
controlBody = blockStatement
The rest of this section describes the core components of a control block.
16.1. Match-action pipeline machine
We can describe the computational model of a match-action pipeline, embodied by a control block: the body of the control block is executed, similarly to the execution of a traditional imperative program:
-
At runtime, statements within a block are executed in the order they appear in the control block.
-
Execution of the
returnstatement causes immediate termination of the execution of the currentcontrolblock, and a return to the caller. -
Execution of the
exitstatement causes the immediate termination of the execution of the currentcontrolblock and of all the enclosing callercontrolblocks. -
Applying a
tableexecutes the corresponding match-action unit.
16.1.1. Sub-controls
P4 allows controls to invoke the services of other controls, similar to
subroutines. To invoke the services of another control, it must be first
instantiated; the services of an instance are invoked by calling it using its
apply method.
The following example shows a control invocation:
control Callee(inout IPv4 ipv4) { /* body omitted */ }
control Caller(inout Headers h) {
Callee() instance; // instance of callee
apply {
instance.apply(h.ipv4); // invoke control
}
}
As with parsers, when a control is instantiated, local instantiations of stateful objects are evaluated recursively. That is, each instantiation of a control has a unique set of local tables, extern objects, inner control instances, etc. Thus, in general, invoking a control instance twice is not the same as invoking two copies of the same control instance. Note however, that local variables do not persist across invocations of the control. This semantics also applies to direct invocation (see Section 13.5).
When a control is instantiated, all its local declarations of stateful instantiations are evaluated recursively. Each instantiation of a control will have a unique set of local tables, extern objects, and inner control instances. Thus, invoking a control instance twice is different from invoking two control instances each once, where the former accesses the same local stateful constructs while the latter access two different copies.
The exactly-once evaluation only applies to local stateful instantiations.
For local variable declarations, whether in the apply block or out, and
whether with initializers or not, they are always evaluated when a control
instance is invoked. That is, local variables in a control never persist
across invocations. For variables declared outside the apply block, they
are evaluated at the beginning of execution.
All the behavior above also applies to direct invocation (see Section 13.5).
16.1.2. Runtime evaluation of a control
At runtime, a control is evaluated by invoking its apply method. The following
algorithm describes the evaluation of a control apply method invocation:
Click to view the specification source
rulegroup Call_eval/controlApplyMethodCallee:
rule Call_eval/controlApplyMethodCallee-copyin-exit:
p EC_0 ARCH_0 |- controlApplyMethodCallee @ `<typeArgumentListIR> `(argumentListIR) : EC_1 ARCH_1 EXIT
-- if CONTROL _ . APPLY `(parameterListIR # id_default*) `{theta ; frame ; controlLocalDeclarationListIR ; actionDefEnv ; controlBodyIR} = controlApplyMethodCallee
-- if EC_callee_0 = $inherit_e(GLOBAL, EC_0)
-- if `{(callableId : actionDef)*} = actionDefEnv
-- if EC_callee_1 = EC_callee_0[BLOCK.TYPE = theta][BLOCK.FRAME = frame][BLOCK.CALLABLE = `{(callableId : actionDef)*}]
-- if GIVEN parameterIR_aligned* DEFAULT parameterIR_default* = $align_parameterListIR(parameterListIR, argumentListIR, id_default*, eps)
-- if p_shared = GLOBAL
-- Copy_in: p_shared ARCH_0 |- p EC_0 parameterIR_aligned* @ BLOCK EC_callee_1 argumentListIR ~> ARCH_1 EC_1 EC_callee_2 # EXIT
rule Call_eval/controlApplyMethodCallee-localDecl-exit:
p EC_0 ARCH_0 |- controlApplyMethodCallee @ `<typeArgumentListIR> `(argumentListIR) : EC_2 ARCH_2 EXIT
-- if CONTROL _ . APPLY `(parameterListIR # id_default*) `{theta ; frame ; controlLocalDeclarationListIR ; actionDefEnv ; controlBodyIR} = controlApplyMethodCallee
-- if EC_callee_0 = $inherit_e(GLOBAL, EC_0)
-- if `{(callableId : actionDef)*} = actionDefEnv
-- if EC_callee_1 = EC_callee_0[BLOCK.TYPE = theta][BLOCK.FRAME = frame][BLOCK.CALLABLE = `{(callableId : actionDef)*}]
-- if GIVEN parameterIR_aligned* DEFAULT parameterIR_default* = $align_parameterListIR(parameterListIR, argumentListIR, id_default*, eps)
-- if p_shared = GLOBAL
-- Copy_in: p_shared ARCH_0 |- p EC_0 parameterIR_aligned* @ BLOCK EC_callee_1 argumentListIR ~> ARCH_1 EC_1 EC_callee_2 # (` storageReference?*)
-- Copy_in_default: parameterIR_default* @ BLOCK EC_callee_2 ~> EC_callee_3
-- ControlLocalDecls_eval: EC_callee_3 ARCH_1 |- controlLocalDeclarationListIR : EC_callee_4 ARCH_2 EXIT
-- Copy_out: p_shared ARCH_2 |- p EC_1 parameterIR_aligned* @ BLOCK EC_callee_4 storageReference?* ~> EC_2
rule Call_eval/controlApplyMethodCallee-controlBodyIR-exit:
p EC_0 ARCH_0 |- controlApplyMethodCallee @ `<typeArgumentListIR> `(argumentListIR) : EC_2 ARCH_3 EXIT
-- if CONTROL _ . APPLY `(parameterListIR # id_default*) `{theta ; frame ; controlLocalDeclarationListIR ; actionDefEnv ; controlBodyIR} = controlApplyMethodCallee
-- if EC_callee_0 = $inherit_e(GLOBAL, EC_0)
-- if `{(callableId : actionDef)*} = actionDefEnv
-- if EC_callee_1 = EC_callee_0[BLOCK.TYPE = theta][BLOCK.FRAME = frame][BLOCK.CALLABLE = `{(callableId : actionDef)*}]
-- if GIVEN parameterIR_aligned* DEFAULT parameterIR_default* = $align_parameterListIR(parameterListIR, argumentListIR, id_default*, eps)
-- if p_shared = GLOBAL
-- Copy_in: p_shared ARCH_0 |- p EC_0 parameterIR_aligned* @ BLOCK EC_callee_1 argumentListIR ~> ARCH_1 EC_1 EC_callee_2 # (` storageReference?*)
-- Copy_in_default: parameterIR_default* @ BLOCK EC_callee_2 ~> EC_callee_3
-- ControlLocalDecls_eval: EC_callee_3 ARCH_1 |- controlLocalDeclarationListIR : EC_callee_4 ARCH_2 `EMPTY
-- ControlBody_eval: EC_callee_4 ARCH_2 |- controlBodyIR : EC_callee_5 ARCH_3 EXIT
-- Copy_out: p_shared ARCH_3 |- p EC_1 parameterIR_aligned* @ BLOCK EC_callee_5 storageReference?* ~> EC_2
rule Call_eval/controlApplyMethodCallee-cont:
p EC_0 ARCH_0 |- controlApplyMethodCallee @ `<typeArgumentListIR> `(argumentListIR) : EC_2 ARCH_3 (RETURN eps)
-- if CONTROL _ . APPLY `(parameterListIR # id_default*) `{theta ; frame ; controlLocalDeclarationListIR ; actionDefEnv ; controlBodyIR} = controlApplyMethodCallee
-- if EC_callee_0 = $inherit_e(GLOBAL, EC_0)
-- if `{(callableId : actionDef)*} = actionDefEnv
-- if EC_callee_1 = EC_callee_0[BLOCK.TYPE = theta][BLOCK.FRAME = frame][BLOCK.CALLABLE = `{(callableId : actionDef)*}]
-- if GIVEN parameterIR_aligned* DEFAULT parameterIR_default* = $align_parameterListIR(parameterListIR, argumentListIR, id_default*, eps)
-- if p_shared = GLOBAL
-- Copy_in: p_shared ARCH_0 |- p EC_0 parameterIR_aligned* @ BLOCK EC_callee_1 argumentListIR ~> ARCH_1 EC_1 EC_callee_2 # (` storageReference?*)
-- Copy_in_default: parameterIR_default* @ BLOCK EC_callee_2 ~> EC_callee_3
-- ControlLocalDecls_eval: EC_callee_3 ARCH_1 |- controlLocalDeclarationListIR : EC_callee_4 ARCH_2 `EMPTY
-- ControlBody_eval: EC_callee_4 ARCH_2 |- controlBodyIR : EC_callee_5 ARCH_3 (RETURN eps)
-- Copy_out: p_shared ARCH_3 |- p EC_1 parameterIR_aligned* @ BLOCK EC_callee_5 storageReference?* ~> EC_2
16.2. Control local declarations
Control local declarations are defined as follows:
controlLocalDeclaration
: constantDeclaration
| instantiation
| variableDeclaration
| actionDeclaration
| tableDeclaration
;
controlLocalDeclarationList
: /* empty */
| controlLocalDeclarationList controlLocalDeclaration
;
16.2.1. Semantics of parser local declarations
16.2.1.1. Type checking
Click to view the specification source
relation ControlLocalDecl_ok: typingContext |- controlLocalDeclaration : typingContext controlLocalDeclarationIR
After type checking, control local declarations are represented in P4IR as follows:
controlLocalDeclarationIR
: constantDeclarationIR
| instantiationIR
| variableDeclarationIR
| actionDeclarationIR
| tableDeclarationIR
;
A list of control local declarations is type checked by:
Click to view the specification source
relation ControlLocalDeclList_ok: typingContext |- controlLocalDeclarationList : typingContext controlLocalDeclarationIR*
Click to view the specification source
rulegroup ControlLocalDeclList_ok: rule ControlLocalDeclList_ok: TC_0 |- controlLocalDeclarationList : TC_1 controlLocalDeclarationIR* -- if controlLocalDeclaration* = $flatten_controlLocalDeclarationList(controlLocalDeclarationList) -- ControlLocalDecls_ok: TC_0 |- controlLocalDeclaration* : TC_1 controlLocalDeclarationIR*
Click to view the specification source
relation ControlLocalDecls_ok: typingContext |- controlLocalDeclaration* : typingContext controlLocalDeclarationIR*
Click to view the specification source
rulegroup ControlLocalDecls_ok: rule ControlLocalDecls_ok/nil: TC |- eps : TC eps rule ControlLocalDecls_ok/cons: TC_0 |- controlLocalDeclaration_h :: controlLocalDeclaration_t* : TC_2 (controlLocalDeclarationIR_h :: controlLocalDeclarationIR_t*) -- ControlLocalDecl_ok: TC_0 |- controlLocalDeclaration_h : TC_1 controlLocalDeclarationIR_h -- ControlLocalDecls_ok: TC_1 |- controlLocalDeclaration_t* : TC_2 controlLocalDeclarationIR_t*
16.2.1.2. Compile-time evaluation
Click to view the specification source
relation ControlLocalDecl_inst: instContext store |- controlLocalDeclarationIR : instContext store controlLocalDeclarationIR?
A list of control local declarations is compile-time evaluated by:
Click to view the specification source
relation ControlLocalDecls_inst: instContext store |- controlLocalDeclarationIR* : instContext store controlLocalDeclarationIR*
Click to view the specification source
rulegroup ControlLocalDecls_inst: rule ControlLocalDecls_inst/nil: IC STO |- eps : IC STO eps rule ControlLocalDecls_inst/cons-none: IC_0 STO_0 |- controlLocalDeclarationIR_h :: controlLocalDeclarationIR_t* : IC_2 STO_2 controlLocalDeclarationIR_t_inst* -- ControlLocalDecl_inst: IC_0 STO_0 |- controlLocalDeclarationIR_h : IC_1 STO_1 eps -- ControlLocalDecls_inst: IC_1 STO_1 |- controlLocalDeclarationIR_t* : IC_2 STO_2 controlLocalDeclarationIR_t_inst* rule ControlLocalDecls_inst/cons-some: IC_0 STO_0 |- controlLocalDeclarationIR_h :: controlLocalDeclarationIR_t* : IC_2 STO_2 (controlLocalDeclarationIR_h_inst :: controlLocalDeclarationIR_t_inst*) -- ControlLocalDecl_inst: IC_0 STO_0 |- controlLocalDeclarationIR_h : IC_1 STO_1 controlLocalDeclarationIR_h_inst -- ControlLocalDecls_inst: IC_1 STO_1 |- controlLocalDeclarationIR_t* : IC_2 STO_2 controlLocalDeclarationIR_t_inst*
16.2.1.3. Runtime evaluation
Click to view the specification source
relation ControlLocalDecl_eval: evalContext arch |- controlLocalDeclarationIR : evalContext arch controlLocalDeclarationResult
Evaluation of a control local declaration yields:
controlLocalDeclarationResult
: continueEmptyResult
| exitResult
;
continueEmptyResult
: /* empty */
;
exitResult
: EXIT
;
A list of control local declarations is runtime evaluated by:
Click to view the specification source
relation ControlLocalDecls_eval: evalContext arch |- controlLocalDeclarationIR* : evalContext arch controlLocalDeclarationResult
Click to view the specification source
rulegroup ControlLocalDecls_eval: rule ControlLocalDecls_eval/nil: EC ARCH |- eps : EC ARCH `EMPTY rule ControlLocalDecls_eval/cons-head-exit: EC_0 ARCH_0 |- controlLocalDeclarationIR_h :: controlLocalDeclarationListIR_t : EC_1 ARCH_1 EXIT -- ControlLocalDecl_eval: EC_0 ARCH_0 |- controlLocalDeclarationIR_h : EC_1 ARCH_1 EXIT rule ControlLocalDecls_eval/cons-head-cont: EC_0 ARCH_0 |- controlLocalDeclarationIR_h :: controlLocalDeclarationListIR_t : EC_2 ARCH_2 controlLocalDeclarationResult -- ControlLocalDecl_eval: EC_0 ARCH_0 |- controlLocalDeclarationIR_h : EC_1 ARCH_1 `EMPTY -- ControlLocalDecls_eval: EC_1 ARCH_1 |- controlLocalDeclarationListIR_t : EC_2 ARCH_2 controlLocalDeclarationResult
The subsequent sections describe each kind of declaration in detail. table
declarations are special and are separately described in
Section 16.3.
16.2.2. Constant declarations
See Section 11.3 for general information about constant declarations.
16.2.2.1. Type checking
Click to view the specification source
rulegroup ControlLocalDecl_ok/constantDeclaration: rule ControlLocalDecl_ok/constantDeclaration: TC_0 |- constantDeclaration : TC_1 constantDeclarationIR -- ConstDecl_ok: BLOCK TC_0 |- constantDeclaration : TC_1 constantDeclarationIR
16.2.2.2. Compile-time evaluation
Click to view the specification source
rulegroup ControlLocalDecl_inst/constantDeclarationIR: rule ControlLocalDecl_inst/constantDeclarationIR: IC_0 STO |- constantDeclarationIR : IC_1 STO constantDeclarationIR -- ConstDecl_inst: BLOCK IC_0 |- constantDeclarationIR : IC_1
16.2.2.3. Runtime evaluation
Click to view the specification source
rulegroup ControlLocalDecl_eval/constantDeclarationIR: rule ControlLocalDecl_eval/constantDeclarationIR: EC_0 ARCH |- constantDeclarationIR : EC_1 ARCH `EMPTY -- ConstDecl_eval: BLOCK EC_0 |- constantDeclarationIR : EC_1
16.2.3. Instantiations
See Section 11.4 for general information about instantiations.
16.2.3.1. Type checking
Click to view the specification source
rulegroup ControlLocalDecl_ok/instantiation: rule ControlLocalDecl_ok/instantiation: TC_0 |- instantiation : TC_1 instantiationIR -- InstDecl_ok: BLOCK TC_0 |- instantiation : TC_1 instantiationIR
16.2.3.2. Compile-time evaluation
Click to view the specification source
rulegroup ControlLocalDecl_inst/instantiationIR: rule ControlLocalDecl_inst/instantiationIR: IC_0 STO_0 |- instantiationIR : IC_1 STO_1 constantDeclarationIR -- InstDecl_inst: BLOCK IC_0 STO_0 |- instantiationIR : IC_1 STO_1 constantDeclarationIR
16.2.4. Variable declarations
See Section 11.2 for general information about variable declarations.
16.2.4.1. Type checking
Click to view the specification source
rulegroup ControlLocalDecl_ok/variableDeclaration: rule ControlLocalDecl_ok/variableDeclaration: TC_0 |- variableDeclaration : TC_1 variableDeclarationIR -- VarDecl_ok: BLOCK TC_0 |- variableDeclaration : TC_1 variableDeclarationIR
16.2.4.2. Compile-time evaluation
Click to view the specification source
rulegroup ControlLocalDecl_inst/variableDeclarationIR: rule ControlLocalDecl_inst/variableDeclarationIR: IC STO |- variableDeclarationIR : IC STO variableDeclarationIR
16.2.4.3. Runtime evaluation
Click to view the specification source
rulegroup ControlLocalDecl_eval/variableDeclarationIR: rule ControlLocalDecl_eval/exit: EC_0 ARCH_0 |- variableDeclarationIR : EC_1 ARCH_1 EXIT -- VarDecl_eval: BLOCK EC_0 ARCH_0 |- variableDeclarationIR : EC_1 ARCH_1 EXIT rule ControlLocalDecl_eval/cont: EC_0 ARCH_0 |- variableDeclarationIR : EC_1 ARCH_1 `EMPTY -- VarDecl_eval: BLOCK EC_0 ARCH_0 |- variableDeclarationIR : EC_1 ARCH_1 `EMPTY
16.2.5. Action declarations
See Section 11.6 for general information about action declarations.
16.2.5.1. Type checking
Click to view the specification source
rulegroup ControlLocalDecl_ok/actionDeclaration: rule ControlLocalDecl_ok/actionDeclaration: TC_0 |- actionDeclaration : TC_1 actionDeclarationIR -- ActionDecl_ok: BLOCK TC_0 |- actionDeclaration : TC_1 actionDeclarationIR
16.2.5.2. Compile-time evaluation
Click to view the specification source
rulegroup ControlLocalDecl_inst/actionDeclarationIR: rule ControlLocalDecl_inst/actionDeclarationIR: IC_0 STO |- actionDeclarationIR : IC_1 STO eps -- ActionDecl_inst: BLOCK IC_0 |- actionDeclarationIR : IC_1
16.3. Match-action tables
tables are the primary mechanism for implementing match-action behavior
in P4 programs. A table performs a lookup based on a key computed from the
packet header fields and other metadata, and applies an action associated
with the matching entry in the table.
A table is declared with standard properties and target-specific custom properties.
tableDeclaration
: annotationList TABLE name `{ tablePropertyList }
;
tablePropertyList
: /* empty */
| tablePropertyList tableProperty
;
tableProperty
: KEY = `{ tableKeyList }
| ACTIONS = `{ tableActionList }
| annotationList constOpt ENTRIES = `{ tableEntryList }
| annotationList constOpt tableCustomName initializer ;
;
The standard table properties include:
-
key: An expression that describes how the key used for look-up is computed. -
actions: A list of all actions that may be found in the table.
In addition, the tables may optionally define the following properties,
-
default_action: an action to execute when the lookup in the lookup table fails to find a match for the key used. -
size: an integer specifying the desired size of the table. -
entries: entries that are initially added to a table when the P4 program is loaded, some or all of which may be unchangeable by the control plane software. -
largest_priority_wins: Only useful for some tables with theentriesproperty. See [sec-table-entries] for details. -
priority_delta: Only useful for some tables with theentriesproperty. See [sec-table-entries] for details.
The compiler must set the default_action to NoAction (and also insert it
into the list of actions) for tables that do not define the default_action
property. Hence, all tables can be thought of as having a default_action
property, either implicitly or explicitly.
In addition, tables may contain architecture-specific properties (see [sec-additional-table-properties]).
A property marked as const cannot be changed dynamically by the control
plane. The key, actions, and size properties cannot be modified so the
const keyword is not needed for these.
16.3.1. Semantics of match-action table properties
16.3.1.1. Type checking
Table properties are type checked with the relation:
Click to view the specification source
relation TableProperty_ok: typingContext tableContext |- tableProperty : tableContext tablePropertyIR
A list of table properties are type checked as:
Click to view the specification source
relation TableProperties_ok: typingContext tableContext |- tableProperty* : tableContext tablePropertyListIR
Click to view the specification source
rulegroup TableProperties_ok: rule TableProperties_ok/nil: TC TBLC |- eps : TBLC eps rule TableProperties_ok/cons: TC TBLC_0 |- (tableProperty_h :: tableProperty_t*) : TBLC_2 (tablePropertyIR_h :: tablePropertyIR_t*) -- TableProperty_ok: TC TBLC_0 |- tableProperty_h : TBLC_1 tablePropertyIR_h -- TableProperties_ok: TC TBLC_1 |- tableProperty_t* : TBLC_2 tablePropertyIR_t*
16.3.1.2. Compile-time evaluation
Table properties are compile-time evaluated with the relation:
Click to view the specification source
relation TableProperty_inst: instContext store |- tablePropertyIR : store tablePropertyIR
After type checking, table properties are represented in P4IR as:
tablePropertyListIR = tablePropertyIR*
tablePropertyIR
: tableKeysPropertyIR
| tableActionsPropertyIR
| tableDefaultActionPropertyIR
| tableEntriesPropertyIR
| tableCustomPropertyIR
;
A list of table properties are compile-time evaluated as:
Click to view the specification source
relation TableProperties_inst: instContext store |- tablePropertyIR* : store tablePropertyIR*
Click to view the specification source
rulegroup TableProperties_inst: rule TableProperties_inst/nil: IC STO |- eps : STO eps rule TableProperties_inst/cons: IC STO_0 |- tablePropertyIR_h :: tablePropertyIR_t* : STO_2 (tablePropertyIR_h_inst :: tablePropertyIR_t_inst*) -- TableProperty_inst: IC STO_0 |- tablePropertyIR_h : STO_1 tablePropertyIR_h_inst -- TableProperties_inst: IC STO_1 |- tablePropertyIR_t* : STO_2 tablePropertyIR_t_inst*
During compile-time evaluation, local instances within table properties are instantiated.
The following subsections describe the standard properties of tables in more detail.
16.3.2. Table keys
The key is a table property which specifies the data-plane values that
should be used to look up an entry. A key is a list of pairs of the form
(e : m), where e is an expression that describes the data to be
matched in the table, and m is a match_kind that describes the algorithm
used to perform the lookup (see Section Section 8.2.5).
tableKeyList
: /* empty */
| tableKeyList tableKey
;
tableKey
: expression : name annotationList ;
;
For example, consider the following program fragment:
table Fwd {
key = {
ipv4header.dstAddress : ternary;
ipv4header.version : exact;
}
// more fields omitted
}
Here the key comprises two fields from the ipv4header
header: dstAddress and version. The match_kind elements
serve three purposes:
-
They specify the algorithm used to match data-plane values against the entries in the table at runtime.
-
They are used to synthesize the control-plane API that is used to populate the table.
-
They are used by the compiler back-end to allocate resources for the implementation of the table.
The P4 core library contains three predefined match_kind identifiers:
match_kind {
exact,
ternary,
lpm
}
These identifiers correspond to the P414 match kinds with the same names. The semantics of these match kinds is actually not needed to describe the behavior of the P4 abstract machine; how they are used influences only the control-plane API and the implementation of the look-up table. From the point of view of the P4 program, a look-up table is an abstract finite map that is given a key and produces as a result either an action or a "miss" indication, as described in Section 16.3.7.
The expected meaning of these values is as follows:
-
an
exactmatch kind on a key field means that the value of the field in the table specifies exactly the value the lookup key field must have in order to match. This is applicable for all legal key fields whose types support equality comparisons. -
a
ternarymatch kind on a key field means that the field in the table specifies a set of values for the key field using a value and a mask. The meaning of the(value, mask)pair is similar to the P4 mask expressions, as described in Section 8.6.5.1.3: a key fieldkmatches the table entry whenk & mask == value & mask. -
a
lpm(longest prefix match) match kind on a key field is a specific type ofternarymatch where the mask is required to have a form in binary that is a contiguous set of 1 bits followed by a contiguous set of 0 bits. Masks with more 1 bits have automatically higher priorities. A mask with all bits 0 is legal.
Some table entries, in particular the ones with at least one ternary field,
also require a priority value. A priority is a numeric value which is used to
break ties when a particular key belongs to multiple sets. When table entries
are specified in the P4 program the priorities are generated by the compiler;
when entries are specified by the control-plane, the priority may need to be
explicitly specified. Entries with higher priority are matched first. This
specification does not mandate whether "higher" priorities are represented by
higher or lower numeric values; this choice is left to the target
implementation.
An example specifying entries for a table is given in [sec-table-entries].
If a table has no key property, or if the value of its key property is the
empty tuple, i.e. key = {}, then it contains no look-up table, just a default
action—i.e., the associated lookup table is always the empty map.
Each key element can have an optional @name annotation which is used to
synthesize the control-plane-visible name for the key field.
Note some implementations might only support a limited number of keys or a limited combinations of match_kind for the keys. The implementation should reject those cases with an error message in this case.
16.3.2.1. Type checking
A table key property is type checked as follows:
Click to view the specification source
rulegroup TableProperty_ok/key:
rule TableProperty_ok/key:
TC TBLC_0 |- KEY = `{tableKeyList} : TBLC_1 (KEY = `{tableKeyIR*})
-- if tableKey* = $flatten_tableKeyList(tableKeyList)
-- TableKeys_ok: TC TBLC_0 |- tableKey* : TBLC_1 tableKeyIR*
After type checking, a table key property is represented in P4IR as:
tableKeysPropertyIR
: KEY = `{ tableKeyListIR }
;
tableKeyListIR = tableKeyIR*
tableKeyIR
: typedExpressionIR : nameIR annotationList ;
;
Table keys are type checked with the following relation:
Click to view the specification source
relation TableKeys_ok: typingContext tableContext |- tableKey* : tableContext tableKeyListIR
Click to view the specification source
rulegroup TableKeys_ok: rule TableKeys_ok/nil: TC TBLC |- eps : TBLC eps rule TableKeys_ok/cons: TC TBLC_0 |- (tableKey_h :: tableKey_t*) : TBLC_2 (tableKeyIR_h :: tableKeyIR_t*) -- TableKey_ok: TC TBLC_0 |- tableKey_h : TBLC_1 tableKeyIR_h -- TableKeys_ok: TC TBLC_1 |- tableKey_t* : TBLC_2 tableKeyIR_t*
The relation invokes the following relation for each key:
Click to view the specification source
relation TableKey_ok: typingContext tableContext |- tableKey : tableContext tableKeyIR
Click to view the specification source
rulegroup TableKey_ok: rule TableKey_ok: TC TBLC_0 |- expression : name_matchkind annotationList ; : TBLC_2 tableKeyIR -- Expr_ok: LOCAL TC |- expression : typedExpressionIR -- if typedExpressionIR_reduced = $reduce_serenum(typedExpressionIR) -- if typeIR_reduced = $type_of_typedExpressionIR(typedExpressionIR_reduced) -- Type_wf: $bound(LOCAL, TC) |- SET `<typeIR_reduced> -- if nameIR_matchkind = $name(name_matchkind) -- if MATCH_KIND . nameIR_matchkind = $find_var_value_t(` nameIR_matchkind, LOCAL, TC) -- if $compat_table_key(nameIR_matchkind, typeIR_reduced) -- if TBLC_1 = $update_mode_tbl(TBLC_0, nameIR_matchkind, typeIR_reduced) -- if TBLC_2 = $add_key_tbl(TBLC_1, nameIR_matchkind, typeIR_reduced) -- if tableKeyIR = typedExpressionIR_reduced : nameIR_matchkind annotationList ;
16.3.2.2. Compile-time evaluation
Click to view the specification source
rulegroup TableProperty_inst/tableKeysPropertyIR: rule TableProperty_inst/tableKeysPropertyIR: IC STO |- tableKeysPropertyIR : STO tableKeysPropertyIR
16.3.2.3. Runtime evaluation
A table key is evaluated at runtime as follows:
Click to view the specification source
relation TableKey_eval: evalContext arch |- tableKeyIR : evalContext arch tableKeyResult
The result of evaluating a table key is:
tableKeyResult
: continueResult<tableKeyValue>
| exitResult
;
tableKeyValue
: value : nameIR
;
exitResult
: EXIT
;
Click to view the specification source
rulegroup TableKey_eval: rule TableKey_eval/exit: EC_0 ARCH_0 |- typedExpressionIR : nameIR _ ; : EC_1 ARCH_1 EXIT -- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 EXIT rule TableKey_eval/cont: EC_0 ARCH_0 |- typedExpressionIR : nameIR _ ; : EC_1 ARCH_1 (` tableKeyValue) -- Expr_eval: LOCAL EC_0 ARCH_0 |- typedExpressionIR : EC_1 ARCH_1 (` value) -- if tableKeyValue = value : nameIR
A list of table keys is evaluated at runtime as follows:
Click to view the specification source
relation TableKeys_eval: evalContext arch |- tableKeyListIR : evalContext arch tableKeysResult
The result of evaluating table keys is:
tableKeysResult
: continueResult<tableKeyValue*>
| exitResult
;
Click to view the specification source
rulegroup TableKeys_eval: rule TableKeys_eval/nil: EC ARCH |- eps : EC ARCH (` eps) rule TableKeys_eval/cons-head-exit: EC_0 ARCH_0 |- tableKeyIR_h :: tableKeyIR_t* : EC_1 ARCH_1 EXIT -- TableKey_eval: EC_0 ARCH_0 |- tableKeyIR_h : EC_1 ARCH_1 EXIT rule TableKeys_eval/cons-head-cont-tail-exit: EC_0 ARCH_0 |- tableKeyIR_h :: tableKeyIR_t* : EC_2 ARCH_2 EXIT -- TableKey_eval: EC_0 ARCH_0 |- tableKeyIR_h : EC_1 ARCH_1 (` tableKeyValue_h) -- TableKeys_eval: EC_1 ARCH_1 |- tableKeyIR_t* : EC_2 ARCH_2 EXIT rule TableKeys_eval/cons-head-cont-tail-cont: EC_0 ARCH_0 |- tableKeyIR_h :: tableKeyIR_t* : EC_2 ARCH_2 (` tableKeyValue*) -- TableKey_eval: EC_0 ARCH_0 |- tableKeyIR_h : EC_1 ARCH_1 (` tableKeyValue_h) -- TableKeys_eval: EC_1 ARCH_1 |- tableKeyIR_t* : EC_2 ARCH_2 (` tableKeyValue_t*) -- if tableKeyValue* = tableKeyValue_h :: tableKeyValue_t*
16.3.3. Table actions
A table must declare all possible actions that may appear within the associated
lookup table or in the default action. This is done with the actions
property; the value of this property is always an actionList:
tableActionList
: /* empty */
| tableActionList tableAction
;
tableAction
: annotationList tableActionReference ;
;
tableActionReference
: prefixedNonTypeName
| prefixedNonTypeName `( argumentList )
;
To illustrate, recall the example Very Simple Switch program in Section 5.3:
action Drop_action() {
outCtrl.outputPort = DROP_PORT;
}
action Rewrite_smac(EthernetAddress sourceMac) {
headers.ethernet.srcAddr = sourceMac;
}
table smac {
key = { outCtrl.outputPort : exact; }
actions = {
Drop_action;
Rewrite_smac;
}
}
-
The entries in the
smactablemay contain two different actions:Drop_actionandRewrite_mac. -
The
Rewrite_smacaction has one parameter,sourceMac, which in this case will be provided by the control plane.
Each action in the list of actions for a table must have a distinct name—e.g., the following program fragment is illegal:
action a() {}
control c() {
action a() {}
// Illegal table: two actions with the same name
table t { actions = { a; .a; } }
}
Each action parameter that has a direction (in, inout, or out) must be
bound in the actions list specification; conversely, no directionless
parameters may be bound in the list. The expressions supplied as arguments to
an action are not evaluated until the action is invoked. Applying tables,
whether directly via an expression like table1.apply().hit, or indirectly,
are forbidden in the expressions supplied as action arguments.
action a(in bit<32> x) { /* body omitted */ }
bit<32> z;
action b(inout bit<32> x, bit<8> data) { /* body omitted */ }
table t {
actions = {
// a; -- illegal, x parameter must be bound
a(5); // binding a's parameter x to 5
b(z); // binding b's parameter x to z
// b(z, 3); -- illegal, cannot bind directionless data parameter
// b(); -- illegal, x parameter must be bound
// a(table2.apply().hit ? 5 : 3); -- illegal, cannot apply a table here
}
}
16.3.3.1. Type checking
A table action property is type checked as follows:
Click to view the specification source
rulegroup TableProperty_ok/actions:
rule TableProperty_ok/actions:
TC TBLC_0 |- ACTIONS = `{tableActionList} : TBLC_1 (ACTIONS = `{tableActionIR*})
-- if tableAction* = $flatten_tableActionList(tableActionList)
-- TableActions_ok: TC TBLC_0 |- tableAction* : TBLC_1 tableActionIR*
After type checking, a table action property is represented in P4IR as:
tableActionsPropertyIR
: ACTIONS = `{ tableActionListIR }
;
tableActionListIR = tableActionIR*
tableActionIR
: annotationList tableActionReferenceIR #
`( parameterListIR , parameterListIR ) ;
;
tableActionReferenceIR
: prefixedNameIR `( argumentListIR )
;
Table actions are type checked with the following relation:
Click to view the specification source
relation TableActions_ok: typingContext tableContext |- tableAction* : tableContext tableActionListIR
Click to view the specification source
rulegroup TableActions_ok: rule TableActions_ok/nil: TC TBLC |- eps : TBLC eps rule TableActions_ok/cons: TC TBLC_0 |- (tableAction_h :: tableAction_t*) : TBLC_2 (tableActionIR_h :: tableActionIR_t*) -- TableAction_ok: TC TBLC_0 |- tableAction_h : TBLC_1 tableActionIR_h -- TableActions_ok: TC TBLC_1 |- tableAction_t* : TBLC_2 tableActionIR_t*
The relation invokes the following relation for each action:
Click to view the specification source
relation TableAction_ok: typingContext tableContext |- tableAction : tableContext tableActionIR
Click to view the specification source
rulegroup TableAction_ok: rule TableAction_ok/prefixedNonTypeName: TC TBLC_0 |- annotationList prefixedNonTypeName ; : TBLC_1 tableActionIR -- if prefixedNameIR = $prefixedNonTypeName(prefixedNonTypeName) -- if (_, annotationList_action ACTION _ `(parameterIR*)) = $find_callableDef_non_overloaded_t(LOCAL, TC, prefixedNameIR) -- if annotation_name? = $find_name_annotation_opt(annotationList_action) -- if annotationList_update = $add_annotationList(annotationList, annotation_name?) -- Call_action_partial_ok: TC |- parameterIR* @ eps : parameterIR_data* , parameterIR_control* @ eps -- if TBLC_1 = $add_action_tbl(TBLC_0, prefixedNameIR, parameterIR*, eps) -- if tableActionIR = annotationList_update (prefixedNameIR `(eps)) # `(parameterIR_data* , parameterIR_control*) ; rule TableAction_ok/prefixedNonTypeName-argumentList: TC TBLC_0 |- annotationList (prefixedNonTypeName `(argumentList)) ; : TBLC_1 tableActionIR -- if prefixedNameIR = $prefixedNonTypeName(prefixedNonTypeName) -- if (_, annotationList_action ACTION _ `(parameterIR*)) = $find_callableDef_non_overloaded_t(LOCAL, TC, prefixedNameIR) -- if annotation_name? = $find_name_annotation_opt(annotationList_action) -- if annotationList_update = $add_annotationList(annotationList, annotation_name?) -- ArgumentList_ok: LOCAL TC |- argumentList : argumentIR* -- Call_action_partial_ok: TC |- parameterIR* @ argumentIR* : parameterIR_data* , parameterIR_control* @ argumentIR_cast* -- if TBLC_1 = $add_action_tbl(TBLC_0, prefixedNameIR, parameterIR*, argumentIR_cast*) -- if tableActionIR = annotationList_update (prefixedNameIR `(argumentIR_cast*)) # `(parameterIR_data* , parameterIR_control*) ;
To check that each action conforms to the calling convention, the following relation is used:
Click to view the specification source
relation Call_action_partial_ok: typingContext |- parameterIR* @ argumentListIR : parameterIR* , parameterIR* @ argumentListIR
Click to view the specification source
rulegroup Call_action_partial_ok: rule Call_action_partial_ok: TC |- parameterIR* @ argumentIR* : parameterIR_data* , parameterIR_control* @ argumentIR_cast* -- if (parameterIR_data*, parameterIR_control*) = $split_dataplane_parameters(parameterIR*) -- if |parameterIR_data*| = |argumentIR*| -- if typedExpressionIR_sub* = $subexpressions_of_argumentListIR(argumentIR*) -- if $forall_((~$is_table_application(typedExpressionIR_sub))*) -- Call_convention_ok: LOCAL TC ACTION |- parameterIR_data* @ argumentIR* : argumentIR_cast*
16.3.3.2. Compile-time evaluation
Click to view the specification source
rulegroup TableProperty_inst/tableActionsPropertyIR: rule TableProperty_inst/tableActionsPropertyIR: IC STO |- tableActionsPropertyIR : STO tableActionsPropertyIR
16.3.4. Table default actions
The default action for a table is an action that is invoked automatically by the match-action unit whenever the lookup table does not find a match for the supplied key.
If present, the default_action property must appear after the action
property. It may be declared as const, indicating that it cannot be changed
dynamically by the control-plane. The default action must be one of the
actions that appear in the actions list. In particular, the expressions passed
as in, out, or inout parameters must be syntactically identical to the
expressions used in one of the elements of the actions list.
For example, in the above table we could set the default action as follows
(marking it also as constant):
const default_action = Rewrite_smac(48w0xAA_BB_CC_DD_EE_FF);
Note that the specified default action must supply arguments for the
control-plane-bound parameters (i.e., the directionless parameters), since the
action is synthesized at compilation time. The expressions supplied as
arguments for parameters with a direction (in, inout, or out) are
evaluated when the action is invoked while the expressions supplied as
arguments for directionless parameters are evaluated at compile time.
Continuing the example from the previous section, the following are several
legal and illegal specifications of default actions for the table t:
default_action = a(5); // OK - no control-plane parameters
// default_action = a(z); -- illegal, a's x parameter is already bound to 5
default_action = b(z,8w8); // OK - bind b's data parameter to 8w8
// default_action = b(z); -- illegal, b's data parameter is not bound
// default_action = b(x, 3); -- illegal: x parameter of b bound to x instead of z
16.3.4.1. Type checking
A table default action property is type checked as follows:
Click to view the specification source
rulegroup TableProperty_ok/default-action: rule TableProperty_ok/default-action: TC TBLC |- annotationList constOpt tableCustomName initializer ; : TBLC tablePropertyIR -- if "default_action" = $tableCustomName(tableCustomName) -- TableDefaultAction_ok: TC TBLC |- initializer : tableActionReferenceIR -- if constOptIR = $flatten_constOpt(constOpt) -- if tablePropertyIR = annotationList constOptIR DEFAULT_ACTION = tableActionReferenceIR ;
After type checking, a table default action property is represented in P4IR as:
tableDefaultActionPropertyIR
: annotationList constOptIR DEFAULT_ACTION = tableActionReferenceIR ;
;
tableActionReferenceIR
: prefixedNameIR `( argumentListIR )
;
Table default actions are type checked with the following relation:
Click to view the specification source
relation TableDefaultAction_ok: typingContext tableContext |- initializer : tableActionReferenceIR
Click to view the specification source
rulegroup TableDefaultAction_ok: rule TableDefaultAction_ok/prefixedNonTypeName: TC TBLC |- = prefixedNonTypeName : prefixedNameIR `(eps) -- if prefixedNameIR = $prefixedNonTypeName(prefixedNonTypeName) -- if (eps, eps) = $find_action(TBLC, prefixedNameIR) rule TableDefaultAction_ok/prefixedNonTypeName-argumentList: TC TBLC |- = (prefixedNonTypeName `(argumentList)) : prefixedNameIR `(argumentIR_cast*) -- if prefixedNameIR = $prefixedNonTypeName(prefixedNonTypeName) -- if (parameterIR_action*, argumentIR_action*) = $find_action(TBLC, prefixedNameIR) -- ArgumentList_ok: LOCAL TC |- argumentList : argumentIR* -- Call_action_default_ok: TC |- parameterIR_action* @ argumentIR* : parameterIR_action_data* , parameterIR_action_control* @ argumentIR_cast* -- if argumentIR_action_data* = argumentIR_action*[0 : |parameterIR_action_data*|] -- if argumentIR_cast_data* = argumentIR_cast*[0 : |parameterIR_action_data*|] -- (if (argumentIR_action_data = argumentIR_cast_data))*
To check that a default action conforms to the calling convention, the following relation is used:
Click to view the specification source
relation Call_action_default_ok: typingContext |- parameterIR* @ argumentListIR : parameterIR* , parameterIR* @ argumentListIR
Click to view the specification source
rulegroup Call_action_partial_ok: rule Call_action_partial_ok: TC |- parameterIR* @ argumentIR* : parameterIR_data* , parameterIR_control* @ argumentIR_cast* -- if (parameterIR_data*, parameterIR_control*) = $split_dataplane_parameters(parameterIR*) -- if |parameterIR_data*| = |argumentIR*| -- if typedExpressionIR_sub* = $subexpressions_of_argumentListIR(argumentIR*) -- if $forall_((~$is_table_application(typedExpressionIR_sub))*) -- Call_convention_ok: LOCAL TC ACTION |- parameterIR_data* @ argumentIR* : argumentIR_cast*
16.3.4.2. Compile-time evaluation
Click to view the specification source
rulegroup TableProperty_inst/tableDefaultActionPropertyIR: rule TableProperty_inst/tableDefaultActionPropertyIR: IC STO |- tableDefaultActionPropertyIR : STO tableDefaultActionPropertyIR
16.3.5. Table entries
While table entries are typically installed by the control plane, tables may also be initialized at compile time with a set of entries.
Declaring these entries with const entries is useful in situations where
tables are used to implement fixed algorithms—defining table entries
statically enables expressing these algorithms directly in P4, which allows the
compiler to infer how the table is actually used and potentially make better
allocation decisions for targets with limited resources.
Declaring entries with entries (without the const qualifier) enables one to
specify a mix of some immutable entries that are always in the table, and some
mutable entries that the control plane is allowed to later change or remove.
Entries declared in the P4 source are installed in the table when the program is loaded onto the target. Entries cannot be specified for a table with no key (see Section 16.3.2).
Table entries are defined using the following syntax:
tableEntryList
: /* empty */
| tableEntryList tableEntry
;
tableEntry
: constOpt tableEntryPriority keysetExpression : tableActionReference
annotationList ;
| constOpt keysetExpression : tableActionReference annotationList ;
;
tableEntryPriority
: PRIORITY = integerLiteral :
| PRIORITY = `( expression ) :
;
Table entries defined using const entries are immutable—i.e., they can only
be read by the control plane. The control plane is not allowed to remove or
modify any entries defined within const entries, nor is it allowed to add
entries to such a table. It is allowed for individual entries to have the
const keyword before them, but this is redundant when the entries are
declared using const entries.
Table entries defined using entries (without a const qualifier before it)
may have const before them, or not, independently for each entry. Entries
with const before them may not be modified or removed by the control plane.
Entries without const may be modified or removed by the control plane. It is
permitted for the control plane to add entries to such a table (subject to
table capacity limitations), unlike tables declared with const entries.
Whether the control plane is allowed to modify a table’s default action at run
time is determined by the table’s default_action table property (see
Section 16.3.4), independently of whether the control plane is
allowed to modify the entries of the table.
The keysetExpression component of an entry is a tuple that must provide a
field for each key in the table keys (see [sec-table-props]). The table key
type must match the type of the element of the set. The tableActionReference
component must be an action which appears in the table actions list (and must
not have the @defaultonly annotation), with all its arguments bound.
If no entry priorities are specified in the source code, and if the runtime API
requires a priority for the entries of a table—e.g. when using the P4 Runtime
API, tables with at least one ternary search key field—then the entries are
matched in program order, stopping at the first matching entry. Architectures
should define the significance of entry order (if any) for other kinds of
tables.
Because control-plane APIs cannot insert or remove entries of a table that is
declared with const entries, the relative priorities of such a table’s
entries are determined solely by the program order of the entries. Therefore
assigning numeric priorities to entries of a table that has const entries is
not allowed.
Depending on the match_kind of the keys, key set expressions may define one
or multiple entries. The compiler will synthesize the correct number of entries
to be installed in the table. Target constraints may further restrict the
ability of synthesizing entries. For example, if the number of synthesized
entries exceeds the table size, the compiler implementation may choose to issue
a warning or an error, depending on target capabilities.
To illustrate, consider the following example:
header hdr {
bit<8> e;
bit<16> t;
bit<8> l;
bit<8> r;
bit<1> v;
}
struct Header_t {
hdr h;
}
struct Meta_t {}
control ingress(inout Header_t h, inout Meta_t m,
inout standard_metadata_t standard_meta) {
action a() { standard_meta.egress_spec = 0; }
action a_params(bit<9> x) { standard_meta.egress_spec = x; }
table t_exact_ternary {
key = {
h.h.e : exact;
h.h.t : ternary;
}
actions = {
a;
a_params;
}
default_action = a;
const entries = {
(0x01, 0x1111 &&& 0xF ) : a_params(1);
(0x02, 0x1181 ) : a_params(2);
(0x03, 0x1111 &&& 0xF000) : a_params(3);
(0x04, 0x1211 &&& 0x02F0) : a_params(4);
(0x04, 0x1311 &&& 0x02F0) : a_params(5);
(0x06, _ ) : a_params(6);
_ : a;
}
}
}
In this example we define a set of 7 entries, all of which invoke action
a_params except for the final entry which invokes action a. Once the
program is loaded, these entries are installed in the table in the order they
are enumerated in the program.
16.3.5.1. Entry priorities
If a table has fields where their match_kinds are all exact or lpm,
there is no reason to assign numeric priorities to its entries. If they are all
exact, duplicate keys are not allowed, and thus every lookup key can match at
most one entry, so there is no need for a tiebreaker. If there is an lpm
field, the priority of the entry corresponds to the length of the prefix, i.e.
if a lookup key matches multiple prefixes, the longest prefix is always the
winner.
For tables with other match_kind values, e.g. at least one ternary field,
in general it is possible to install multiple entries such that the same lookup
key can match the key of multiple entries installed into the table at the same
time. Control plane APIs such as P4Runtime API [2] and TDI
[4] require control plane software to provide
a numeric priority with each entry added to such a table. This enables the data
plane to determine which of several matching entries is the "winner", i.e. the
one entry whose action is invoked.
Unfortunately there are two commonly used, but different, ways of interpreting numeric priority values.
The P4Runtime API requires numeric priorities to be positive integers, i.e. 1
or larger, and defines that entries with larger priorities must win over
entries with smaller priorities. We will call this convention
largest_priority_wins.
TDI requires numeric priorities to be non-negative integers, i.e. 0 or larger,
and defines that entries with smaller priorities must win over entries with
larger priorities. We will call this convention smallest_priority_wins.
We wish to support either of these conventions when developers specify
priorities for initial table entries in the program. Thus there is a table
property largest_priority_wins. If explicitly specified for a table, its
value must be boolean. If true, then the priority values use the
largest_priority_wins convention. If false, then the priority values use
the smallest_priority_wins convention. If the table property is not present
at all, then the default convention is true, corresponding to
largest_priority_wins.
We also wish to support developers that want the convenience of predictable entry priority values automatically selected by the compiler, without having to write them in the program, plus the ability to specify entry priorities explicitly, if they wish.
In some cases, developers may wish the initial priority values to have "gaps"
between their values, to leave room for possible later insertion of new entries
between two initial entries. They can achieve this by explicitly specifying all
priority values, of course, but as a convenience we define the table property
priority_delta to be a positive integer value, with a default value of 1 if
not specified for a table, to use as a default difference between the
priorities of consecutive entries.
There are two steps that occur at compile time for a table with the entries
property involving entry priorities:
-
Determine the value of the priority of every entry in the
entrieslist. -
Issue any errors or warnings that are appropriate for these priority values. Warnings may be suppressed via an appropriate
@noWarnannotation.
These steps are performed independently for each table with the entries
property, and each is described in more detail below.
In general, if the developer specifies a priority value for an entry, that is the value that will be used.
If the developer does not specify priority values for any entry, then the compiler calculates priority values for every entry as follows:
// For this pseudocode, table entries in the `entries` list are
// numbered 0 through n-1, 0 being the first to appear in order in the
// source code. Their priority values are named prio[0] through
// prio[n-1].
int p = 1;
if (largest_priority_wins == true) {
for (int j = n-1; j >= 0; j -= 1) {
prio[j] = p;
p += priority_delta;
}
} else {
for (int j = 0; j < n; j += 1) {
prio[j] = p;
p += priority_delta;
}
}
If the developer specifies priority values for at least one entry, then in order to simplify the rules for determining priorities of entries without one in the source code, the first entry must have a priority value explicitly provided. The priorities of entries that do not have one in the source code (if any) are determined as follows:
// Same conventions here as in the previous block of pseudocode above.
// If entry j has a priority value specified in the source code,
// prio_specified[j] is true, otherwise it is false.
assert(prio_specified[0]); // compile time error if prio_specified[0] is false
p = prio[0];
for (int j = 1; j < n; j += 1) {
if (prio_specified[j]) {
p = prio[j];
} else {
if (largest_priority_wins == true) {
p -= priority_delta;
} else {
p += priority_delta;
}
prio[j] = p;
}
}
This is the end of the first step: determining entry priorities.
The priorities determined in this way are the values used when the P4 program is first loaded into a device. Afterwards, the priorities may only change by means provided by the control plane API in use.
In the second step, the compiler issues errors for out of range priority values, and/or warnings for certain combinations of entry priorities that might be unintended by the developer, unless the developer explicitly disables those warnings.
If any priority values are negative, or larger than the maximum supported value, that is a compile time error.
If the annotation @noWarn("duplicate_priorities") is not used on the
entries table property, then the compiler issues a warning if any two entries
for the same table have equal priority values. Both P4Runtime and TDI leave it
unspecified which entry is the winner if a lookup key matches multiple keys
that all have the same priority, hence a warning is useful to less experienced
developers that are unfamiliar with this unspecified behavior.
If the annotation @noWarn("duplicate_priorities") is used on the entries
table property, then no warnings of this type are ever issued by the compiler.
Using equal priority values for multiple entries in the same table is sometimes
useful in reducing the number of hardware updates required when adding entries
to such a table.
If the annotation @noWarn("entries_out_of_priority_order") is not used on
the entries table property, then the compiler issues a warning if:
-
If
largest_priority_winsistruefor the table, and there is any pair of consecutive entries whereprio[j] < prio[j+1], then a warning is issued for that pair of entries. -
If
largest_priority_winsisfalsefor the table, and there is any pair of consecutive entries whereprio[j] > prio[j+1], then a warning is issued for that pair of entries.
This warning is useful to developers that want the order that entries appear in the source code to match the relative priority of entries in the target device.
If the annotation @noWarn("entries_out_of_priority_order") is used on the
entries table property, then no warnings of this type are ever issued by the
compiler for this table. his option is provided for developers who explicitly
choose to specify entries in an order that does not match their relative
priority order.
The following example is the same as the first example in [sec-entries],
except for the definition of table t_exact_ternary shown below.
table t_exact_ternary {
key = {
h.h.e : exact;
h.h.t : ternary;
}
actions = {
a;
a_params;
}
default_action = a;
largest_priority_wins = false;
priority_delta = 10;
@noWarn("duplicate_priorities")
entries = {
const priority=10: (0x01, 0x1111 &&& 0xF ) : a_params(1);
(0x02, 0x1181 ) : a_params(2); // priority=20
(0x03, 0x1000 &&& 0xF000) : a_params(3); // priority=30
const (0x04, 0x0210 &&& 0x02F0) : a_params(4); // priority=40
priority=40: (0x04, 0x0010 &&& 0x02F0) : a_params(5);
(0x06, _ ) : a_params(6); // priority=50
}
}
The entries that do not have an explicit priority specified will be assigned
the priority values shown in the comments, because priority_delta is 10, and
because of those entries that do have priority values specified.
Normally this program would cause a warning about multiple entries with the
same priority of 40, but those warnings will be suppressed because of the
@noWarn("duplicate_priorities") annotation.
16.3.6. Additional properties
A table declaration defines its essential control and data plane
interfaces—i.e., keys and actions. However, the best way to implement a table
may actually depend on the nature of the entries that will be installed at
runtime (for example, tables could be dense or sparse, could be implemented as
hash-tables, associative memories, tries, etc.) In addition, some architectures
may support extra table properties whose semantics lies outside the scope of
this specification. For example, in architectures where table resources are
statically allocated, programmers may be required to define a size table
property, which can be used by the compiler back-end to allocate storage
resources. However, these architecture-specific properties may not change the
semantics of table lookups, which always produce either a hit and an action
or a miss--they can only change how those results are interpreted on the
state of the data plane. This restriction is needed to ensure that it is
possible to reason about the behavior of tables during compilation.
As another example, an implementation property could be used to pass
additional information to the compiler back-end. The value of this property
could be an instance of an extern block chosen from a suitable library of
components. For example, the core functionality of the P414 table
action_profile constructs could be implemented on architectures that support
this feature using a construct such as the following:
extern ActionProfile {
ActionProfile(bit<32> size); // number of distinct actions expected
}
table t {
key = { /* body omitted */ }
size = 1024;
implementation = ActionProfile(32); // constructor invocation
}
Here the action profile might be used to optimize for the case where the table has a large number of entries, but the actions associated with those entries are expected to range over a small number of distinct values. Introducing a layer of indirection enables sharing identical entries, which can significantly reduce the table’s storage requirements.
16.3.6.1. Type checking
Additional properties, including size, is type checked as follows:
Click to view the specification source
rulegroup TableProperty_ok/size-and-others: rule TableProperty_ok/size: TC TBLC |- annotationList constOpt tableCustomName (= expression) ; : TBLC tablePropertyIR -- if "size" = $tableCustomName(tableCustomName) -- Expr_ok: LOCAL TC |- expression : typedExpressionIR -- if typeIR = $type_of_typedExpressionIR(typedExpressionIR) -- if typeIR_unroll = $unroll_typeIR(typeIR) -- if (typeIR_unroll <: integerTypeIR) /\ ~(typeIR_unroll <: varBitTypeIR) -- if constOptIR = $flatten_constOpt(constOpt) -- if tablePropertyIR = annotationList constOptIR CUSTOM "size" (= typedExpressionIR) ; rule TableProperty_ok/others: TC TBLC |- annotationList constOpt tableCustomName (= expression) ; : TBLC tablePropertyIR -- if nameIR = $tableCustomName(tableCustomName) -- if nameIR =/= "default_action" /\ nameIR =/= "size" /\ nameIR =/= "largest_priority_wins" /\ nameIR =/= "priority_delta" -- Expr_ok: LOCAL TC |- expression : typedExpressionIR -- if constOptIR = $flatten_constOpt(constOpt) -- if tablePropertyIR = annotationList constOptIR CUSTOM nameIR (= typedExpressionIR) ;
After type checking, these are represented in P4IR as:
tableCustomPropertyIR
: annotationList constOptIR CUSTOM nameIR initializerIR ;
| annotationList constOptIR CUSTOM_CONST nameIR constantInitializerIR ;
;
16.3.6.2. Compile-time evaluation
Click to view the specification source
rulegroup TableProperty_inst/tableCustomPropertyIR: rule TableProperty_inst/custom: IC STO_0 |- annotationList constOptIR CUSTOM nameIR (= typedExpressionIR) ; : STO_1 tableCustomPropertyIR_inst -- if IC_inner = $enter_path_i(IC, nameIR) -- Expr_inst: LOCAL IC_inner STO_0 |- typedExpressionIR : STO_1 value -- if tableCustomPropertyIR_inst = annotationList constOptIR CUSTOM_CONST nameIR (= `VALUE value) ; rule TableProperty_inst/custom-const: IC STO |- annotationList constOptIR CUSTOM_CONST nameIR constantInitializerIR ; : STO tableCustomPropertyIR_inst -- if tableCustomPropertyIR_inst = annotationList constOptIR CUSTOM_CONST nameIR constantInitializerIR ;
16.3.7. Match-action table invocation
The semantics of a table invocation statement:
t.apply();
is given by the following pseudocode (see also Figure 11):
apply_result(m) m.apply() {
apply_result(m) result;
var lookupKey = m.buildKey(m.key); // using key block
action RA = m.table.lookup(lookupKey);
if (RA == null) { // miss in lookup table
result.hit = false;
RA = m.default_action; // use default action
}
else {
result.hit = true;
}
result.miss = !result.hit;
result.action_run = action_type(RA);
evaluate_and_copy_in_RA_args(RA);
execute(RA);
copy_out_RA_args(RA);
return result;
}
The behavior of the buildKey call in the pseudocode above is to evaluate each
key expression in the order they appear in the table key definition. The
behavior must be the same as if the result of evaluating each key expression is
assigned to a fresh temporary variable, before starting the evaluation of the
following key expression. For example, this P4 table definition and apply call:
bit<8> f1 (in bit<8> a, inout bit<8> b) {
b = a + 5;
return a >> 1;
}
bit<8> x;
bit<8> y;
table t1 {
key = {
y & 0x7 : exact @name("masked_y");
f1(x, y) : exact @name("f1");
y : exact;
}
// ... rest of table properties defined here, not relevant to example
}
apply {
// assign values to x and y here, not relevant to example
t1.apply();
}
is equivalent in behavior to the following table definition and apply call:
// same definition of f1, x, and y as before, so they are not repeated here
bit<8> tmp_1;
bit<8> tmp_2;
bit<8> tmp_3;
table t1 {
key = {
tmp_1 : exact @name("masked_y");
tmp_2 : exact @name("f1");
tmp_3 : exact @name("y");
}
// ... rest of table properties defined here, not relevant to example
}
apply {
// assign values to x and y here, not relevant to example
tmp_1 = y & 0x7;
tmp_2 = f1(x, y);
tmp_3 = y;
t1.apply();
}
Note that the second code example above is given in order to specify the behavior of the first one. An implementation is free to choose any technique that achieves this behavior.[5]
The following algorithm describes the evaluation of a table apply method invocation:
Click to view the specification source
rulegroup Call_eval/tableApplyMethodCallee:
rule Call_eval/tableApplyMethodCallee-exit:
p EC_0 ARCH_0 |- tableApplyMethodCallee @ `<typeArgumentListIR> `(argumentListIR) : EC_1 ARCH_1 EXIT
-- if TABLE objectId . APPLY `{frame ; TBL} = tableApplyMethodCallee
-- if EC_callee_0 = $inherit_e(BLOCK, EC_0)
-- if EC_callee_1 = EC_callee_0[LOCAL.FRAMES = [frame]]
-- if typeId :: _ = $rev_<id>(objectId)
-- Table_eval: EC_callee_1 ARCH_0 |- typeId TBL : EC_callee_2 ARCH_1 EXIT
-- if EC_1 = $copy_e(BLOCK, EC_callee_2, EC_0)
rule Call_eval/tableApplyMethodCallee-return:
p EC_0 ARCH_0 |- tableApplyMethodCallee @ `<typeArgumentListIR> `(argumentListIR) : EC_1 ARCH_1 returnResult
-- if TABLE objectId . APPLY `{frame ; TBL} = tableApplyMethodCallee
-- if EC_callee_0 = $inherit_e(BLOCK, EC_0)
-- if EC_callee_1 = EC_callee_0[LOCAL.FRAMES = [frame]]
-- if typeId :: _ = $rev_<id>(objectId)
-- Table_eval: EC_callee_1 ARCH_0 |- typeId TBL : EC_callee_2 ARCH_1 returnResult
-- if EC_1 = $copy_e(BLOCK, EC_callee_2, EC_0)
The table match-action is implemented as:
Click to view the specification source
relation Table_eval: evalContext arch |- typeId tableObjectProperty : evalContext arch tableResult
${rulegroup-title-source: Table_eval} ${rulegroup-title-prose: Table_eval}
17. Casting
P4 provides a limited set of casts between types. While this design is arguably more onerous for programmers, it has several benefits:
-
It makes user intent unambiguous.
-
It makes the costs associated with converting numeric values explicit. Implementing certain casts involves sign extensions, and thus can require significant computational resources on some targets.
-
It reduces the number of cases that have to be considered in the P4 specification. Some targets may not support all casts.
17.1. Explicit casts
The below relation checks whether a type can be explicitly cast to another type.
Click to view the specification source
relation Cast_expl: typeIR -> typeIR
First, the two types are unrolled and checked to see if they are the same type. If so, no cast is needed.
Click to view the specification source
rulegroup Cast_expl: rule Cast_expl/equals: typeIR_a -> typeIR_b -- if typeIR_a_unroll = $unroll_typeIR(typeIR_a) -- if typeIR_b_unroll = $unroll_typeIR(typeIR_b) -- Type_alpha: typeIR_a_unroll ~~ typeIR_b_unroll rule Cast_expl/not-equals: typeIR_a -> typeIR_b -- if typeIR_a_unroll = $unroll_typeIR(typeIR_a) -- if typeIR_b_unroll = $unroll_typeIR(typeIR_b) -- Type_alpha:/ typeIR_a_unroll ~~ typeIR_b_unroll -- Cast_expl_neq: typeIR_a_unroll -> typeIR_b_unroll
If the two types are not equal, the below relation is applied.
Click to view the specification source
relation Cast_expl_neq: typeIR -> typeIR
Cast from bool to bit<1>
Click to view the specification source
rulegroup Cast_expl_neq/boolean-fixBit: rule Cast_expl_neq/boolean-fixBit: BOOL -> BIT `(1)
Cast from int to bool, int<S>, or bit<W>
Click to view the specification source
rulegroup Cast_expl_neq/arbitraryInt: rule Cast_expl_neq/boolean: INT -> BOOL rule Cast_expl_neq/fixInt: INT -> INT `<_> rule Cast_expl_neq/fixBit: INT -> BIT `<_>
Cast from int<S> to int, int<T>, or bit<W>
Click to view the specification source
rulegroup Cast_expl_neq/fixInt: rule Cast_expl_neq/arbitraryInt: INT `<_> -> INT rule Cast_expl_neq/fixInt: INT `<w_a> -> INT `<w_b> rule Cast_expl_neq/fixBit: INT `<w> -> BIT `<w>
Cast from bit<W> to bool, int, int<S>, or bit<X>
Click to view the specification source
rulegroup Cast_expl_neq/fixBit: rule Cast_expl_neq/boolean: BIT `<1> -> BOOL rule Cast_expl_neq/arbitraryInt: BIT `<_> -> INT rule Cast_expl_neq/fixInt: BIT `<w> -> INT `<w> rule Cast_expl_neq/fixBit: BIT `<w_a> -> BIT `<w_b>
Cast from a new type to its underlying type, and vice versa
Click to view the specification source
rulegroup Cast_expl_neq/newTypeIR: rule Cast_expl_neq/left: TYPE _ typeIR_a -> typeIR_b -- Cast_impl: typeIR_a -> typeIR_b rule Cast_expl_neq/right: typeIR_a -> TYPE _ typeIR_b -- Cast_impl: typeIR_a -> typeIR_b
Cast from an enum with an underlying type to the underlying type, and vice versa
Click to view the specification source
rulegroup Cast_expl_neq/enumTypeIR-serializable:
rule Cast_expl_neq/left:
ENUM _ `<typeIR_a> `{_} -> typeIR_b
-- Cast_impl: typeIR_a -> typeIR_b
rule Cast_expl_neq/right:
typeIR_a -> ENUM _ `<typeIR_b> `{_}
-- Cast_impl: typeIR_a -> typeIR_b
Cast from a default type to the target type
Click to view the specification source
rulegroup Cast_expl_neq/defaultTypeIR: rule Cast_expl_neq/defaultTypeIR: DEFAULT -> typeIR_b -- if $is_defaultable_typeIR(typeIR_b)
Cast from an invalid header type to a header or header union type
Click to view the specification source
rulegroup Cast_expl_neq/invalidHeaderTypeIR: rule Cast_expl_neq/headerTypeIR: HEADER_INVALID -> headerTypeIR rule Cast_expl_neq/headerUnionTypeIR: HEADER_INVALID -> headerUnionTypeIR
Cast from a sequence type to a list, tuple, header stack, struct, header, or another sequence type
Click to view the specification source
rulegroup Cast_expl_neq/sequenceTypeIR-non-default:
rule Cast_expl_neq/listTypeIR:
SEQ `<typeIR_a*> -> LIST `<typeIR_b>
-- (Cast_expl: typeIR_a -> typeIR_b)*
rule Cast_expl_neq/tupleTypeIR:
SEQ `<typeIR_a*> -> TUPLE `<typeIR_b*>
-- (Cast_expl: typeIR_a -> typeIR_b)*
rule Cast_expl_neq/headerStackTypeIR:
SEQ `<typeIR_a*> -> typeIR_b `[n_size]
-- if |typeIR_a*| <= n_size
-- (Cast_expl: typeIR_a -> typeIR_b)*
rule Cast_expl_neq/structTypeIR:
SEQ `<typeIR_a*> -> STRUCT _ `<_> `{(_ typeIR_field_b _ ;)*}
-- (Cast_expl: typeIR_a -> typeIR_field_b)*
rule Cast_expl_neq/headerTypeIR:
SEQ `<typeIR_a*> -> HEADER _ `<_> `{(_ typeIR_field_b _ ;)*}
-- (Cast_expl: typeIR_a -> typeIR_field_b)*
Cast from a sequence type with default initializer to a tuple, header stack, struct, or header type
Click to view the specification source
rulegroup Cast_expl_neq/sequenceTypeIR-default:
rule Cast_expl_neq/tupleTypeIR:
SEQ `<typeIR_a* , ...> -> TUPLE `<typeIR_b*>
-- if |typeIR_a*| < |typeIR_b*|
-- if (typeIR_b_non_default*, typeIR_b_default*) = $partition_<typeIR>(typeIR_b*, |typeIR_a*|)
-- (Cast_expl: typeIR_a -> typeIR_b_non_default)*
-- (if $is_defaultable_typeIR(typeIR_b_default))*
rule Cast_expl_neq/headerStackTypeIR:
SEQ `<typeIR_a* , ...> -> typeIR_b `[n_size]
-- if |typeIR_a*| < n_size
-- (Cast_expl: typeIR_a -> typeIR_b)*
-- if $is_defaultable_typeIR(typeIR_b)
rule Cast_expl_neq/structTypeIR:
SEQ `<typeIR_a* , ...> -> STRUCT _ `<_> `{(_ typeIR_field_b _ ;)*}
-- if |typeIR_a*| < |typeIR_field_b*|
-- if (typeIR_field_b_non_default*, typeIR_field_b_default*) = $partition_<typeIR>(typeIR_field_b*, |typeIR_a*|)
-- (Cast_expl: typeIR_a -> typeIR_field_b_non_default)*
-- (if $is_defaultable_typeIR(typeIR_field_b_default))*
rule Cast_expl_neq/headerTypeIR:
SEQ `<typeIR_a* , ...> -> HEADER _ `<_> `{(_ typeIR_field_b _ ;)*}
-- if |typeIR_a*| < |typeIR_field_b*|
-- if (typeIR_field_b_non_default*, typeIR_field_b_default*) = $partition_<typeIR>(typeIR_field_b*, |typeIR_a*|)
-- (Cast_expl: typeIR_a -> typeIR_field_b_non_default)*
-- (if $is_defaultable_typeIR(typeIR_field_b_default))*
Cast from a record type to a struct or header type
Click to view the specification source
rulegroup Cast_expl_neq/recordTypeIR-non-default:
rule Cast_expl_neq/structTypeIR:
RECORD `{(_ typeIR_field_a id_field_a ;)*} -> STRUCT _ `<_> `{(_ typeIR_field_b id_field_b ;)*}
-- if $eq_set<id>(`{id_field_a*}, `{id_field_b*})
-- (if (typeIR_field_a_aligned = $find_map<id, typeIR>(`{(id_field_a : typeIR_field_a)*}, id_field_a)))*
-- (if (typeIR_field_b_aligned = $find_map<id, typeIR>(`{(id_field_b : typeIR_field_b)*}, id_field_a)))*
-- (Cast_expl: typeIR_field_a_aligned -> typeIR_field_b_aligned)*
rule Cast_expl_neq/headerTypeIR:
RECORD `{(_ typeIR_field_a id_field_a ;)*} -> HEADER _ `<_> `{(_ typeIR_field_b id_field_b ;)*}
-- if $eq_set<id>(`{id_field_a*}, `{id_field_b*})
-- (if (typeIR_field_a_aligned = $find_map<id, typeIR>(`{(id_field_a : typeIR_field_a)*}, id_field_a)))*
-- (if (typeIR_field_b_aligned = $find_map<id, typeIR>(`{(id_field_b : typeIR_field_b)*}, id_field_a)))*
-- (Cast_expl: typeIR_field_a_aligned -> typeIR_field_b_aligned)*
Cast from a record type with default initializer to a struct or header type
Click to view the specification source
rulegroup Cast_expl_neq/recordTypeIR-default:
rule Cast_expl_neq/structTypeIR:
RECORD `{(_ typeIR_field_a id_field_a ;)* , ...} -> STRUCT _ `<_> `{(_ typeIR_field_b id_field_b ;)*}
-- if $sub_set<id>(`{id_field_a*}, `{id_field_b*})
-- (if (typeIR_field_a_aligned = $find_map<id, typeIR>(`{(id_field_a : typeIR_field_a)*}, id_field_a)))*
-- (if (typeIR_field_b_non_default_aligned = $find_map<id, typeIR>(`{(id_field_b : typeIR_field_b)*}, id_field_a)))*
-- (Cast_expl: typeIR_field_a_aligned -> typeIR_field_b_non_default_aligned)*
-- if `{id_default*} = $diff_set<id>(`{id_field_b*}, `{id_field_a*})
-- (if (typeIR_field_b_default = $find_map<id, typeIR>(`{(id_field_b : typeIR_field_b)*}, id_default)))*
-- (if $is_defaultable_typeIR(typeIR_field_b_default))*
rule Cast_expl_neq/headerTypeIR:
RECORD `{(_ typeIR_field_a id_field_a ;)* , ...} -> HEADER _ `<_> `{(_ typeIR_field_b id_field_b ;)*}
-- if $sub_set<id>(`{id_field_a*}, `{id_field_b*})
-- (if (typeIR_field_a_aligned = $find_map<id, typeIR>(`{(id_field_a : typeIR_field_a)*}, id_field_a)))*
-- (if (typeIR_field_b_non_default_aligned = $find_map<id, typeIR>(`{(id_field_b : typeIR_field_b)*}, id_field_a)))*
-- (Cast_expl: typeIR_field_a_aligned -> typeIR_field_b_non_default_aligned)*
-- if `{id_default*} = $diff_set<id>(`{id_field_b*}, `{id_field_a*})
-- (if (typeIR_field_b_default = $find_map<id, typeIR>(`{(id_field_b : typeIR_field_b)*}, id_default)))*
-- (if $is_defaultable_typeIR(typeIR_field_b_default))*
17.2. Implicit casts
To keep the language simple and avoid introducing hidden costs, the main
implicit casts in P4 are casts from int to fixed-width types and from enums
with an underlying type to the underlying type. In particular, applying a
binary operation (except shifts and concatenation) to an expression of type
int and an expression with a fixed-width type will implicitly cast the int
expression to the type of the other expression. For enums with an underlying
type, it can be implicitly cast to its underlying type whenever appropriate,
including but not limited to in shifts, concatenation, bit slicing indexes,
header stack indexes as well as other unary and binary operations.
For example, given the following declarations,
enum bit<8> E {
a = 5
}
bit<8> x;
bit<16> y;
int<8> z;
the compiler will add implicit casts as follows:
-
x + 1becomesx + (bit<8>)1 -
z < 0becomesz < (int<8>)0 -
x | 0xFFFbecomesx | (bit<8>)0xFFF; overflow warning -
x + E.abecomesx + (bit<8>)E.a -
x &&& 8becomesx &&& (bit<8>)8 -
x << 256remains unchanged;256not implicitly cast to8w0in a shift; overflow warning -
16w11 << E.abecomes16w11 << (bit<8>)E.a -
x[E.a:0]becomesx[(bit<8>)E.a:0] -
E.a ++ 8w0becomes(bit<8>)E.a ++ 8w0
The compiler also adds implicit casts when types of different expressions need
to match; for example, as described in Section [sec-select], since select
labels are compared against the selected expression, the compiler will insert
implicit casts for the select labels when they have int types. Similarly,
when assigning a structure-valued expression to a structure or header, the
compiler will add implicit casts for int fields.
The below relation checks whether a type can be implicitly cast to another type.
Click to view the specification source
relation Cast_impl: typeIR -> typeIR
First, the two types are unrolled and checked to see if they are the same type. If so, no cast is needed.
Click to view the specification source
rulegroup Cast_impl: rule Cast_impl/equals: typeIR_a -> typeIR_b -- if typeIR_a_unroll = $unroll_typeIR(typeIR_a) -- if typeIR_b_unroll = $unroll_typeIR(typeIR_b) -- Type_alpha: typeIR_a_unroll ~~ typeIR_b_unroll rule Cast_impl/not-equals: typeIR_a -> typeIR_b -- if typeIR_a_unroll = $unroll_typeIR(typeIR_a) -- if typeIR_b_unroll = $unroll_typeIR(typeIR_b) -- Type_alpha:/ typeIR_a_unroll ~~ typeIR_b_unroll -- Cast_impl_neq: typeIR_a_unroll -> typeIR_b_unroll
If the two types are not equal, the below relation is applied.
Click to view the specification source
relation Cast_impl_neq: typeIR -> typeIR
Cast from int to int<S> or bit<W>
Click to view the specification source
rulegroup Cast_impl_neq/arbitraryInt: rule Cast_impl_neq/fixInt: INT -> INT `<_> rule Cast_impl_neq/fixBit: INT -> BIT `<_>
Cast from an enum with an underlying type to the underlying type
Click to view the specification source
rulegroup Cast_impl_neq/enumTypeIR-serializable:
rule Cast_impl_neq/enumTypeIR-serializable:
ENUM _ `<typeIR_a> `{_} -> typeIR_b
-- Cast_impl: typeIR_a -> typeIR_b
Cast from a default type to the target type
Click to view the specification source
rulegroup Cast_impl_neq/defaultTypeIR: rule Cast_impl_neq/defaultTypeIR: DEFAULT -> typeIR_b -- if $is_defaultable_typeIR(typeIR_b)
Cast from an invalid header type to a header or header union type
Click to view the specification source
rulegroup Cast_impl_neq/invalidHeaderTypeIR: rule Cast_impl_neq/headerTypeIR: HEADER_INVALID -> headerTypeIR rule Cast_impl_neq/headerUnionTypeIR: HEADER_INVALID -> headerUnionTypeIR
Cast from a sequence type to a list, tuple, header stack, struct, header, or another sequence type
Click to view the specification source
rulegroup Cast_impl_neq/sequenceTypeIR-non-default:
rule Cast_impl_neq/listTypeIR:
SEQ `<typeIR_a*> -> LIST `<typeIR_b>
-- (Cast_impl: typeIR_a -> typeIR_b)*
rule Cast_impl_neq/tupleTypeIR:
SEQ `<typeIR_a*> -> TUPLE `<typeIR_b*>
-- (Cast_impl: typeIR_a -> typeIR_b)*
rule Cast_impl_neq/headerStackTypeIR:
SEQ `<typeIR_a*> -> typeIR_b `[n_size]
-- if |typeIR_a*| <= n_size
-- (Cast_impl: typeIR_a -> typeIR_b)*
rule Cast_impl_neq/structTypeIR:
SEQ `<typeIR_a*> -> STRUCT _ `<_> `{(_ typeIR_field_b _ ;)*}
-- (Cast_impl: typeIR_a -> typeIR_field_b)*
rule Cast_impl_neq/headerTypeIR:
SEQ `<typeIR_a*> -> HEADER _ `<_> `{(_ typeIR_field_b _ ;)*}
-- (Cast_impl: typeIR_a -> typeIR_field_b)*
rule Cast_impl_neq/sequenceTypeIR:
SEQ `<typeIR_a*> -> SEQ `<typeIR_b*>
-- (Cast_impl: typeIR_a -> typeIR_b)*
Cast from a sequence type with default initializer to a tuple, header stack, struct, or header type
Click to view the specification source
rulegroup Cast_impl_neq/sequenceTypeIR-default:
rule Cast_impl_neq/tupleTypeIR:
SEQ `<typeIR_a* , ...> -> TUPLE `<typeIR_b*>
-- if |typeIR_a*| < |typeIR_b*|
-- if (typeIR_b_non_default*, typeIR_b_default*) = $partition_<typeIR>(typeIR_b*, |typeIR_a*|)
-- (Cast_impl: typeIR_a -> typeIR_b_non_default)*
-- (if $is_defaultable_typeIR(typeIR_b_default))*
rule Cast_impl_neq/headerStackTypeIR:
SEQ `<typeIR_a* , ...> -> typeIR_b `[n_size]
-- if |typeIR_a*| < n_size
-- (Cast_impl: typeIR_a -> typeIR_b)*
-- if $is_defaultable_typeIR(typeIR_b)
rule Cast_impl_neq/structTypeIR:
SEQ `<typeIR_a* , ...> -> STRUCT _ `<_> `{(_ typeIR_field_b _ ;)*}
-- if |typeIR_a*| < |typeIR_field_b*|
-- if (typeIR_field_b_non_default*, typeIR_field_b_default*) = $partition_<typeIR>(typeIR_field_b*, |typeIR_a*|)
-- (Cast_impl: typeIR_a -> typeIR_field_b_non_default)*
-- (if $is_defaultable_typeIR(typeIR_field_b_default))*
rule Cast_impl_neq/headerTypeIR:
SEQ `<typeIR_a* , ...> -> HEADER _ `<_> `{(_ typeIR_field_b _ ;)*}
-- if |typeIR_a*| < |typeIR_field_b*|
-- if (typeIR_field_b_non_default*, typeIR_field_b_default*) = $partition_<typeIR>(typeIR_field_b*, |typeIR_a*|)
-- (Cast_impl: typeIR_a -> typeIR_field_b_non_default)*
-- (if $is_defaultable_typeIR(typeIR_field_b_default))*
Cast from a record type to a struct or header type
Click to view the specification source
rulegroup Cast_impl_neq/recordTypeIR-non-default:
rule Cast_impl_neq/structTypeIR:
RECORD `{(_ typeIR_field_a id_field_a ;)*} -> STRUCT _ `<_> `{(_ typeIR_field_b id_field_b ;)*}
-- if $eq_set<id>(`{id_field_a*}, `{id_field_b*})
-- (if (typeIR_field_a_aligned = $find_map<id, typeIR>(`{(id_field_a : typeIR_field_a)*}, id_field_a)))*
-- (if (typeIR_field_b_aligned = $find_map<id, typeIR>(`{(id_field_b : typeIR_field_b)*}, id_field_a)))*
-- (Cast_impl: typeIR_field_a_aligned -> typeIR_field_b_aligned)*
rule Cast_impl_neq/headerTypeIR:
RECORD `{(_ typeIR_field_a id_field_a ;)*} -> HEADER _ `<_> `{(_ typeIR_field_b id_field_b ;)*}
-- if $eq_set<id>(`{id_field_a*}, `{id_field_b*})
-- (if (typeIR_field_a_aligned = $find_map<id, typeIR>(`{(id_field_a : typeIR_field_a)*}, id_field_a)))*
-- (if (typeIR_field_b_aligned = $find_map<id, typeIR>(`{(id_field_b : typeIR_field_b)*}, id_field_a)))*
-- (Cast_impl: typeIR_field_a_aligned -> typeIR_field_b_aligned)*
Cast from a record type with default initializer to a struct or header type
Click to view the specification source
rulegroup Cast_impl_neq/recordTypeIR-default:
rule Cast_impl_neq/structTypeIR:
RECORD `{(_ typeIR_field_a id_field_a ;)* , ...} -> STRUCT _ `<_> `{(_ typeIR_field_b id_field_b ;)*}
-- if $sub_set<id>(`{id_field_a*}, `{id_field_b*})
-- (if (typeIR_field_a_aligned = $find_map<id, typeIR>(`{(id_field_a : typeIR_field_a)*}, id_field_a)))*
-- (if (typeIR_field_b_non_default_aligned = $find_map<id, typeIR>(`{(id_field_b : typeIR_field_b)*}, id_field_a)))*
-- (Cast_impl: typeIR_field_a_aligned -> typeIR_field_b_non_default_aligned)*
-- if `{id_default*} = $diff_set<id>(`{id_field_b*}, `{id_field_a*})
-- (if (typeIR_field_b_default = $find_map<id, typeIR>(`{(id_field_b : typeIR_field_b)*}, id_default)))*
-- (if $is_defaultable_typeIR(typeIR_field_b_default))*
rule Cast_impl_neq/headerTypeIR:
RECORD `{(_ typeIR_field_a id_field_a ;)* , ...} -> HEADER _ `<_> `{(_ typeIR_field_b id_field_b ;)*}
-- if $sub_set<id>(`{id_field_a*}, `{id_field_b*})
-- (if (typeIR_field_a_aligned = $find_map<id, typeIR>(`{(id_field_a : typeIR_field_a)*}, id_field_a)))*
-- (if (typeIR_field_b_non_default_aligned = $find_map<id, typeIR>(`{(id_field_b : typeIR_field_b)*}, id_field_a)))*
-- (Cast_impl: typeIR_field_a_aligned -> typeIR_field_b_non_default_aligned)*
-- if `{id_default*} = $diff_set<id>(`{id_field_b*}, `{id_field_a*})
-- (if (typeIR_field_b_default = $find_map<id, typeIR>(`{(id_field_b : typeIR_field_b)*}, id_default)))*
-- (if $is_defaultable_typeIR(typeIR_field_b_default))*
17.3. Cast insertion
When typing a P416 program to a P4IR program, all implicit casts are made explicit. This process is called cast insertion. A cast may be inserted such that an expression matches the expected type, and it may also be inserted to ensure that two expressions have the same type.
17.3.1. Casts to match expected type
The below function inserts casts to match expected types.
Click to view the specification source
dec $cast_unary(typedExpressionIR, typeIR) : typedExpressionIR?
Click to view the specification source
def $cast_unary(typedExpressionIR, typeIR_to) = typedExpressionIR -- if _ # `(typeIR _) = typedExpressionIR -- Type_alpha: typeIR ~~ typeIR_to def $cast_unary(typedExpressionIR, typeIR_to) = typedExpressionIR_cast -- if _ # `(typeIR ctk) = typedExpressionIR -- Type_alpha:/ typeIR ~~ typeIR_to -- Cast_impl: typeIR -> typeIR_to -- if typedExpressionIR_cast = (`(typeIR_to) typedExpressionIR) # `(typeIR_to ctk)
17.3.2. Casts to ensure type equality
The below function inserts casts to ensure that two expressions have the same type.
Click to view the specification source
dec $cast_binary(typedExpressionIR, typedExpressionIR) : (typedExpressionIR, typedExpressionIR)?
Click to view the specification source
def $cast_binary(typedExpressionIR_l, typedExpressionIR_r) = (typedExpressionIR_l, typedExpressionIR_r) -- if _ # `(typeIR_l _) = typedExpressionIR_l -- if _ # `(typeIR_r _) = typedExpressionIR_r -- Type_alpha: typeIR_l ~~ typeIR_r def $cast_binary(typedExpressionIR_l, typedExpressionIR_r) = (typedExpressionIR_l_cast, typedExpressionIR_r) -- if _ # `(typeIR_l ctk_l) = typedExpressionIR_l -- if _ # `(typeIR_r _) = typedExpressionIR_r -- Type_alpha:/ typeIR_l ~~ typeIR_r -- Cast_impl: typeIR_l -> typeIR_r -- if typedExpressionIR_l_cast = (`(typeIR_r) typedExpressionIR_l) # `(typeIR_r ctk_l) def $cast_binary(typedExpressionIR_l, typedExpressionIR_r) = (typedExpressionIR_l, typedExpressionIR_r_cast) -- if _ # `(typeIR_l _) = typedExpressionIR_l -- if _ # `(typeIR_r ctk_r) = typedExpressionIR_r -- Type_alpha:/ typeIR_l ~~ typeIR_r -- Cast_impl:/ typeIR_l -> typeIR_r -- Cast_impl: typeIR_r -> typeIR_l -- if typedExpressionIR_r_cast = (`(typeIR_l) typedExpressionIR_r) # `(typeIR_l ctk_r) def $cast_binary(typedExpressionIR_l, typedExpressionIR_r) = $cast_binary(typedExpressionIR_l_cast, typedExpressionIR_r) -- if _ # `(typeIR_l ctk_l) = typedExpressionIR_l -- if _ # `(typeIR_r _) = typedExpressionIR_r -- Type_alpha:/ typeIR_l ~~ typeIR_r -- Cast_impl:/ typeIR_l -> typeIR_r -- Cast_impl:/ typeIR_r -> typeIR_l -- if typedExpressionIR_l_cast = $reduce_serenum(typedExpressionIR_l) -- if typedExpressionIR_l =/= typedExpressionIR_l_cast def $cast_binary(typedExpressionIR_l, typedExpressionIR_r) = $cast_binary(typedExpressionIR_l, typedExpressionIR_r_cast) -- if _ # `(typeIR_l _) = typedExpressionIR_l -- if _ # `(typeIR_r ctk_r) = typedExpressionIR_r -- Type_alpha:/ typeIR_l ~~ typeIR_r -- Cast_impl:/ typeIR_l -> typeIR_r -- Cast_impl:/ typeIR_r -> typeIR_l -- if typedExpressionIR_l_cast = $reduce_serenum(typedExpressionIR_l) -- if typedExpressionIR_l = typedExpressionIR_l_cast -- if typedExpressionIR_r_cast = $reduce_serenum(typedExpressionIR_r) -- if typedExpressionIR_r =/= typedExpressionIR_r_cast def $cast_binary(typedExpressionIR_l, typedExpressionIR_r) = eps -- otherwise
17.4. Performing casts
Cast operations are evaluated with:
Click to view the specification source
dec $cast_op(typeIR, value) : value
Click to view the specification source
def $cast_op(typeIR, boolValue) = $cast_bool(typeIR_unroll, boolValue) -- if typeIR_unroll = $unroll_typeIR(typeIR) def $cast_op(typeIR, integerValue) = $cast_int(typeIR_unroll, integerValue) -- if typeIR_unroll = $unroll_typeIR(typeIR) def $cast_op(typeIR, errorValue) = $cast_error(typeIR_unroll, errorValue) -- if typeIR_unroll = $unroll_typeIR(typeIR) def $cast_op(typeIR, listValue) = $cast_list(typeIR_unroll, listValue) -- if typeIR_unroll = $unroll_typeIR(typeIR) def $cast_op(typeIR, headerStackValue) = $cast_header_stack(typeIR_unroll, headerStackValue) -- if typeIR_unroll = $unroll_typeIR(typeIR) def $cast_op(typeIR, structValue) = $cast_struct(typeIR_unroll, structValue) -- if typeIR_unroll = $unroll_typeIR(typeIR) def $cast_op(typeIR, headerValue) = $cast_header(typeIR_unroll, headerValue) -- if typeIR_unroll = $unroll_typeIR(typeIR) def $cast_op(typeIR, enumValue) = $cast_enum(typeIR_unroll, enumValue) -- if typeIR_unroll = $unroll_typeIR(typeIR) def $cast_op(typeIR, sequenceValue) = $cast_sequence(typeIR_unroll, sequenceValue) -- if typeIR_unroll = $unroll_typeIR(typeIR) def $cast_op(typeIR, recordValue) = $cast_record(typeIR_unroll, recordValue) -- if typeIR_unroll = $unroll_typeIR(typeIR) def $cast_op(typeIR, DEFAULT) = $cast_default(typeIR_unroll, DEFAULT) -- if typeIR_unroll = $unroll_typeIR(typeIR) def $cast_op(typeIR, invalidHeaderValue) = $cast_invalid_header(typeIR_unroll) -- if typeIR_unroll = $unroll_typeIR(typeIR) def $cast_op(typeIR, setValue) = $cast_set(typeIR_unroll, setValue) -- if typeIR_unroll = $unroll_typeIR(typeIR)
From a bool value
Click to view the specification source
def $cast_bool(BOOL, `B b) = `B b
def $cast_bool(BIT `<w>, `B true) = w W 1
def $cast_bool(BIT `<w>, `B false) = w W 0
def $cast_bool(TYPE _ typeIR, boolValue) = $cast_bool(typeIR, boolValue)
def $cast_bool(SET `<typeIR>, boolValue) = SET `{$cast_bool(typeIR, boolValue)}
From an integer value
Click to view the specification source
def $cast_int(BOOL, D i) = `B (i =/= 0)
def $cast_int(BOOL, w W i) = `B (i =/= 0)
def $cast_int(INT, D i) = D i
def $cast_int(INT, _ W i) = D i
def $cast_int(INT, w S i) = D $bitstr_to_int(w, i)
def $cast_int(BIT `<w_to>, D i) = w_to W i_cast
-- if i_cast = $int_to_bitstr(w_to, i)
def $cast_int(BIT `<w_to>, _ W i) = w_to W i_cast
-- if i_cast = $int_to_bitstr(w_to, i)
def $cast_int(BIT `<w_to>, w_from S i) = w_to W i_cast
-- if i_from = $bitstr_to_int(w_from, i)
-- if i_cast = $int_to_bitstr(w_to, i_from)
def $cast_int(INT `<w_to>, D i) = w_to S i_cast
-- if i_cast = $int_to_bitstr(w_to, i)
def $cast_int(INT `<w_to>, _ W i) = w_to S i_cast
-- if i_cast = $int_to_bitstr(w_to, i)
def $cast_int(INT `<w_to>, w_from S i) = w_to S i_cast
-- if i_from = $bitstr_to_int(w_from, i)
-- if i_cast = $int_to_bitstr(w_to, i_from)
def $cast_int(VARBIT `<w_max>, w_max . w V i) = w_max . w V i
def $cast_int(TYPE _ typeIR, D i) = $cast_op(typeIR, D i)
def $cast_int(TYPE _ typeIR, w W i) = $cast_op(typeIR, w W i)
def $cast_int(TYPE _ typeIR, w S i) = $cast_op(typeIR, w S i)
def $cast_int(enumTypeIR, w W i) = $cast_to_enum(enumTypeIR, w W i)
def $cast_int(enumTypeIR, w S i) = $cast_to_enum(enumTypeIR, w S i)
def $cast_int(SET `<typeIR>, D i) = SET `{$cast_op(typeIR, D i)}
def $cast_int(SET `<typeIR>, w W i) = SET `{$cast_op(typeIR, w W i)}
def $cast_int(SET `<typeIR>, w S i) = SET `{$cast_op(typeIR, w S i)}
From an error value
Click to view the specification source
def $cast_error(ERROR, ERROR . nameIR) = ERROR . nameIR
def $cast_error(SET `<typeIR>, errorValue) = SET `{$cast_op(typeIR, errorValue)}
From a list value
Click to view the specification source
def $cast_list(LIST `<typeIR>, LIST `[value*]) = LIST `[$cast_op(typeIR, value)*]
From a header stack value
Click to view the specification source
def $cast_header_stack(typeIR `[n_size], HEADER_STACK `[value* `(n_idx ; n_size)]) = HEADER_STACK `[value* `(n_idx ; n_size)]
From a struct value
Click to view the specification source
def $cast_struct(STRUCT typeId `<_> `{_}, STRUCT typeId `{(value_field nameIR_field ;)*}) = STRUCT typeId `{(value_field nameIR_field ;)*}
From a header value
Click to view the specification source
def $cast_header(HEADER typeId `<_> `{_}, HEADER typeId `{b ; (value_field nameIR_field ;)*}) = HEADER typeId `{b ; (value_field nameIR_field ;)*}
From an enum value
Click to view the specification source
def $cast_enum(ENUM typeId `{_}, typeId . nameIR) = typeId . nameIR
def $cast_enum(typeIR, _ . _ . value) = $cast_op(typeIR, value)
From a default value
Click to view the specification source
def $cast_default(typeIR, defaultValue) = $default(typeIR)
From an invalid header value
Click to view the specification source
def $cast_invalid_header(headerTypeIR) = $default(headerTypeIR) def $cast_invalid_header(headerUnionTypeIR) = $default(headerUnionTypeIR)
From a sequence value
Click to view the specification source
def $cast_sequence(LIST `<typeIR>, SEQ `(value*)) = LIST `[$cast_op(typeIR, value)*]
def $cast_sequence(TUPLE `<typeIR*>, SEQ `(value*)) = TUPLE `($cast_op(typeIR, value)*)
def $cast_sequence(typeIR `[n_size], SEQ `(value*)) = HEADER_STACK `[value_cast* `(n_idx ; n_size)]
-- (if (value_cast = $cast_op(typeIR, value)))*
-- if n_idx = |value*|
def $cast_sequence(STRUCT typeId `<_> `{(_ typeIR_field nameIR_field ;)*}, SEQ `(value*)) = STRUCT typeId `{(value_cast nameIR_field ;)*}
-- (if (value_cast = $cast_op(typeIR_field, value)))*
def $cast_sequence(HEADER typeId `<_> `{(_ typeIR_field nameIR_field ;)*}, SEQ `(value*)) = HEADER typeId `{true ; (value_cast nameIR_field ;)*}
-- (if (value_cast = $cast_op(typeIR_field, value)))*
From a record value
Click to view the specification source
def $cast_record(STRUCT typeId `<_> `{(_ typeIR_t_field id_t_field ;)*}, RECORD `{(value_field nameIR_field ;)*}) = STRUCT typeId `{(value_field_cast nameIR_field ;)*}
-- (if (value_field' = $find_map<id, value>(`{(nameIR_field : value_field)*}, id_t_field)))*
-- (if (value_field_cast = $cast_op(typeIR_t_field, value_field')))*
def $cast_record(HEADER typeId `<_> `{(_ typeIR_t_field id_t_field ;)*}, RECORD `{(value_field nameIR_field ;)*}) = HEADER typeId `{true ; (value_field_cast nameIR_field ;)*}
-- (if (value_field' = $find_map<id, value>(`{(nameIR_field : value_field)*}, id_t_field)))*
-- (if (value_field_cast = $cast_op(typeIR_t_field, value_field')))*
From a set value
Click to view the specification source
def $cast_set(SET `<typeIR>, SET `{value}) = SET `{$cast_op(typeIR, value)}
def $cast_set(SET `<typeIR>, SET `{value_b &&& value_m}) = SET `{value_b_cast &&& value_m_cast}
-- if value_b_cast = $cast_op(typeIR, value_b)
-- if value_m_cast = $cast_op(typeIR, value_m)
def $cast_set(SET `<typeIR>, SET `{value_l .. value_u}) = SET `{value_l_cast .. value_u_cast}
-- if value_l_cast = $cast_op(typeIR, value_l)
-- if value_u_cast = $cast_op(typeIR, value_u)
18. Calls
Calls can be made in several different contexts:
-
From call expressions (Section 14.16), to invoke a callable or a constructor.
-
From call statements (Section 13.4), to invoke a callable.
-
From a direct type invocation (Section 13.5), which is a syntactic sugar for invoking a constructor and then immediately calling an apply method on the resulting instance.
-
From an instantiation declaration (Section 11.4), to invoke a constructor.
A callable invocation can optionally specify for each argument the corresponding parameter name. It is illegal to use names only for some arguments: either all or no arguments must specify the parameter name. Calalble arguments are evaluated in the order they appear, left to right, before the function invocation takes place.
extern void f(in bit<32> x, out bit<16> y);
bit<32> xa = 0;
bit<16> ya;
f(xa, ya); // match arguments by position
f(x = xa, y = ya); // match arguments by name
f(y = ya, x = xa); // match arguments by name in any order
//f(x = xa); -- error: enough arguments
//f(x = xa, x = ya); -- error: argument specified twice
//f(x = xa, ya); -- error: some arguments specified by name
//f(z = xa, w = yz); -- error: no parameter named z or w
//f(x = xa, y = 0); -- error: y must be a left-value
The calling convention is copy-in/copy-out (Section 6.5).
For generic callables and constructors the type arguments can be explicitly
specified in the call. The compiler only inserts implicit casts for direction
in or directionless arguments to methods, functions, or constructors as
described in [sec-casts]. The types for all other arguments must match the
parameter types exactly.
The result returned by a call is discarded when the function call is used as a statement.
The "don’t care" identifier (_) can only be used for an out function/method
argument, when the value of returned in that argument is ignored by subsequent
computations. When used in generic callables, the compiler may reject the
program if it is unable to infer a type for the don’t care argument.
This section describes the semantics of calls in detail. In particular, it explains how the call target is found (also with overload resolution), how the call convention is checked, and how the call is evaluated at runtime.
18.1. Call targets
Calls are composed of three components: the call target, type arguments, and arguments. Call target identifies what is being called. It may be a callable (actions, functions, and methods) or a constructor.
When calls are made from an expression, the call target is as follows:
callExpression
: callTarget `( argumentList )
| callableTarget `< realTypeArgumentList > `( argumentList )
;
callTarget
: callableTarget
| constructorTarget
;
callableTarget = expression
constructorTarget = namedType
When calls are made from a statement, the call target is as follows:
lvalue
: referenceExpression
| lvalue . member
| lvalue `[ expression ]
| lvalue `[ expression : expression ]
| `( lvalue )
;
callStatement
: lvalue `( argumentList ) ;
| lvalue `< typeArgumentList > `( argumentList ) ;
;
directApplicationStatement
: namedType . APPLY `( argumentList ) ;
;
And when calls are made from instantiation declarations, the call target is as follows:
instantiation
: annotationList type `( argumentList ) name ;
| annotationList type `( argumentList ) name objectInitializer ;
;
After type checking, call targets are represented as follows:
callableTargetIR
: referenceExpressionIR
| typedExpressionIR . nameIR
| TYPE prefixedNameIR . nameIR
| `( callableTargetIR )
;
constructorTargetIR
: prefixedNameIR `< typeArgumentListIR >
;
Thus, calls are represented as:
callExpressionIR
: constructorTargetIR `( argumentListIR )
| callableTargetIR `< typeArgumentListIR > `( argumentListIR )
;
callStatementIR
: callableTargetIR `< typeArgumentListIR > `( argumentListIR ) ;
;
directApplicationStatementIR
: constructorTargetIR . APPLY `( argumentListIR ) ;
;
instantiationIR
: annotationList typeIR constructorTargetIR `( argumentListIR ) nameIR
objectInitializerOptIR ;
;
The following relation is used to check callable targets:
Click to view the specification source
relation CallableTarget_ok: cursor typingContext |- callableTarget : callableTargetIR
Click to view the specification source
rulegroup CallableTarget_ok: rule CallableTarget_ok/prefixedNonTypeName: p TC |- prefixedNonTypeName : prefixedNameIR -- if prefixedNameIR = $prefixedNonTypeName(prefixedNonTypeName) rule CallableTarget_ok/this: p TC |- THIS : (` "this") rule CallableTarget_ok/memberAccessExpression-prefixedTypeName: p TC |- prefixedTypeName . member : TYPE prefixedNameIR . nameIR -- if prefixedNameIR = $prefixedTypeName(prefixedTypeName) -- if nameIR = $name(member) rule CallableTarget_ok/memberAccessExpression-expression: p TC |- expression_base . member : typedExpressionIR_base . nameIR -- if nameIR = $name(member) -- Expr_ok: p TC |- expression_base : typedExpressionIR_base rule CallableTarget_ok/parenthesizedExpression: p TC |- `(expression) : `(callableTargetIR) -- CallableTarget_ok: p TC |- expression : callableTargetIR
When a l-value is used as a callable target, the following relation is used to check it:
Click to view the specification source
relation CallableTarget_lvalue_ok: cursor typingContext |- lvalue : callableTargetIR
Click to view the specification source
rulegroup CallableTarget_lvalue_ok: rule CallableTarget_lvalue_ok: p TC |- lvalue : callableTargetIR -- if expression = $lvalue_as_expression(lvalue) -- CallableTarget_ok: p TC |- expression : callableTargetIR
18.2. Overload resolution
As explained in Section 6.6, functions, methods, and constructors can be overloaded. Overload should be resolved at compile time based on the number of arguments or the names of the arguments. Additionally, arguments may be omitted when the corresponding parameters have default values or are declared as optional.
The following algorithm is used to resolve overloads and omitted arguments:
Click to view the specification source
dec $find_overloaded<V>(map<callTargetId, V>, callTargetKey, $get_parameterListIR(V) : parameterListIR) : callResolution<V>?
The result of this algorithm is a matched overload item with the default and optional parameter names, or an empty result if no match is found.
callResolution<V>
: callableId : V # nameIR* # nameIR*
;
The algorithm takes as input the key for overload resolution, which is the call target name and the argument names (if specified).
callTargetKey
: nameIR `( id?* )
;
Based on whether the arguments are positional or named, the key is refined to either:
namedCallTargetKey
: nameIR `( id* )
;
unnamedCallTargetKey
: nameIR `( nat )
;
For each item in the overloaded set, the key is compared to determine if it is a match:
callTargetMatch
: MATCH id* id*
| NOMATCH
;
The algorithm is as follows:
Click to view the specification source
def $find_overloaded<V>(`{(callableId : V)*}, nameIR_f `(id_arg?*), $get_parameterListIR) = eps
-- if |(id_arg?)*| > 0
-- (if (id_arg_inner = id_arg?))*
-- if eps = $find_overloadeds_named<V>(`{(callableId : V)*}, nameIR_f `(id_arg_inner*), $get_parameterListIR)
def $find_overloaded<V>(`{(callableId : V)*}, nameIR_f `(id_arg?*), $get_parameterListIR) = callableId_found : V_found # id_default* # id_optional*
-- if |(id_arg?)*| > 0
-- (if (id_arg_inner = id_arg?))*
-- if callableId_found : V_found # id_default* # id_optional* = $find_overloadeds_named<V>(`{(callableId : V)*}, nameIR_f `(id_arg_inner*), $get_parameterListIR)
def $find_overloaded<V>(`{(callableId : V)*}, nameIR_f `(id_arg?*), $get_parameterListIR) = eps
-- (if (id_arg? = eps))*
-- if eps = $find_overloadeds_unnamed<V>(`{(callableId : V)*}, nameIR_f `(|id_arg?*|), $get_parameterListIR)
def $find_overloaded<V>(`{(callableId : V)*}, nameIR_f `(id_arg?*), $get_parameterListIR) = callableId_found : V_found # id_omitted* # id_optional*
-- (if (id_arg? = eps))*
-- if callableId_found : V_found # id_omitted* # id_optional* = $find_overloadeds_unnamed<V>(`{(callableId : V)*}, nameIR_f `(|id_arg?*|), $get_parameterListIR)
18.2.1. Positional arguments
The following algorithm is used when arguments are positional:
Click to view the specification source
dec $find_overloadeds_unnamed<V>(map<callableId, V>, unnamedCallTargetKey, $get_parameterListIR(V) : parameterListIR) : callResolution<V>*
Click to view the specification source
def $find_overloadeds_unnamed<V>(`{eps}, nameIR_f `(nat), $get_parameterListIR) = eps
def $find_overloadeds_unnamed<V>(`{(callTargetId_h : V_h) :: (callTargetId_t : V_t)*}, nameIR_f `(n_arg), $get_parameterListIR) = $find_overloadeds_unnamed<V>(`{(callTargetId_t : V_t)*}, nameIR_f `(n_arg), $get_parameterListIR)
-- if NOMATCH = $match_overloaded_unnamed<V>((callTargetId_h : V_h), nameIR_f `(n_arg), $get_parameterListIR)
def $find_overloadeds_unnamed<V>(`{(callTargetId_h : V_h) :: (callTargetId_t : V_t)*}, nameIR_f `(n_arg), $get_parameterListIR) = (callTargetId_h : V_h # id_default* # id_optional*) :: $find_overloadeds_unnamed<V>(`{(callTargetId_t : V_t)*}, nameIR_f `(n_arg), $get_parameterListIR)
-- if MATCH id_default* id_optional* = $match_overloaded_unnamed<V>((callTargetId_h : V_h), nameIR_f `(n_arg), $get_parameterListIR)
18.2.2. Named arguments
The following algorithm is used when arguments are named:
Click to view the specification source
dec $find_overloadeds_named<V>(map<callableId, V>, namedCallTargetKey, $get_parameterListIR(V) : parameterListIR) : callResolution<V>*
Click to view the specification source
def $find_overloadeds_named<V>(`{eps}, nameIR_f `(id_arg*), $get_parameterListIR) = eps
def $find_overloadeds_named<V>(`{(callTargetId_h : V_h) :: (callTargetId_t : V_t)*}, nameIR_f `(id_arg*), $get_parameterListIR) = $find_overloadeds_named<V>(`{(callTargetId_t : V_t)*}, nameIR_f `(id_arg*), $get_parameterListIR)
-- if NOMATCH = $match_overloaded_named<V>((callTargetId_h : V_h), nameIR_f `(id_arg*), $get_parameterListIR)
def $find_overloadeds_named<V>(`{(callTargetId_h : V_h) :: (callTargetId_t : V_t)*}, nameIR_f `(id_arg*), $get_parameterListIR) = (callTargetId_h : V_h # id_default* # id_optional*) :: $find_overloadeds_named<V>(`{(callTargetId_t : V_t)*}, nameIR_f `(id_arg*), $get_parameterListIR)
-- if MATCH id_default* id_optional* = $match_overloaded_named<V>((callTargetId_h : V_h), nameIR_f `(id_arg*), $get_parameterListIR)
18.3. Callee resolution
Given a call target, type arguments, and arguments, the callee should be resolved. This is done both during type checking and runtime evaluation.
18.3.1. Resolving constructors
When type checking, constructors are resolved by:
Click to view the specification source
relation ConstructorType_ok: cursor typingContext |- constructorTargetIR `(argumentIR*) : constructorTypeIR `<# typeId*> `(# id* # id*)
Click to view the specification source
rulegroup ConstructorType_ok:
rule ConstructorType_ok/non-aliased-none-type:
p TC |- (prefixedNameIR `<typeArgumentIR*>) `(argumentIR*) : constructorTypeIR `<# typeId_impl*> `(# id_default* # id_optional*)
-- if eps = $find_typeDef_t(p, TC, prefixedNameIR)
-- if callableId : constructorTypeDefIR # id_default* # id_optional* = $find_constructorDef_overloaded_t(TC, prefixedNameIR, argumentIR*)
-- if (constructorTypeIR, typeId_impl*) = $specialize_constructorTypeDefIR(constructorTypeDefIR, typeArgumentIR*)
-- if B = $union_set<typeId>($bound(p, TC), `{typeId_impl*})
-- ConstructorType_wf: B |- constructorTypeIR
rule ConstructorType_ok/non-aliased-some-type:
p TC |- (prefixedNameIR `<typeArgumentIR*>) `(argumentIR*) : constructorTypeIR `<# typeId_impl*> `(# id_default* # id_optional*)
-- if typeDefIR = $find_typeDef_t(p, TC, prefixedNameIR)
-- if ~(typeDefIR <: typedefTypeIR)
-- if callableId : constructorTypeDefIR # id_default* # id_optional* = $find_constructorDef_overloaded_t(TC, prefixedNameIR, argumentIR*)
-- if (constructorTypeIR, typeId_impl*) = $specialize_constructorTypeDefIR(constructorTypeDefIR, typeArgumentIR*)
-- if B = $union_set<typeId>($bound(p, TC), `{typeId_impl*})
-- ConstructorType_wf: B |- constructorTypeIR
rule ConstructorType_ok/aliased:
p TC |- (prefixedNameIR `<eps>) `(argumentIR*) : constructorTypeIR `<# typeId_impl*> `(# id_default* # id_optional*)
-- if TYPEDEF nameIR_alias typeIR_alias = $find_typeDef_t(p, TC, prefixedNameIR)
-- if (nameIR, typeArgumentIR*) = $resolve_type_alias(typeIR_alias)
-- if callableId : constructorTypeDefIR # id_default* # id_optional* = $find_constructorDef_overloaded_t(TC, ` nameIR, argumentIR*)
-- if (constructorTypeIR, typeId_impl*) = $specialize_constructorTypeDefIR(constructorTypeDefIR, typeArgumentIR*)
-- if B = $union_set<typeId>($bound(p, TC), `{typeId_impl*})
-- ConstructorType_wf: B |- constructorTypeIR
During compile-time evaluation, constructors are resolved by:
Click to view the specification source
relation Constructor_inst: cursor instContext |- prefixedNameIR `<typeArgumentListIR> `(argumentListIR) : constructorDef `<typeArgumentListIR> `(# id* # id*)
Click to view the specification source
rulegroup Constructor_inst: rule Constructor_inst/non-aliased-none-type: p IC |- prefixedNameIR `<typeArgumentIR*> `(argumentIR*) : constructorDef `<typeArgumentIR*> `(# id_default* # id_optional*) -- if eps = $find_typeDef_i(p, IC, prefixedNameIR) -- if _ : constructorDef # id_default* # id_optional* = $find_constructorDef_i(IC, prefixedNameIR, argumentIR*) rule Constructor_inst/non-aliased-some-type: p IC |- prefixedNameIR `<typeArgumentIR*> `(argumentIR*) : constructorDef `<typeArgumentIR*> `(# id_default* # id_optional*) -- if typeDefIR = $find_typeDef_i(p, IC, prefixedNameIR) -- if ~(typeDefIR <: typedefTypeIR) -- if _ : constructorDef # id_default* # id_optional* = $find_constructorDef_i(IC, prefixedNameIR, argumentIR*) rule Constructor_inst/aliased: p IC |- prefixedNameIR `<eps> `(argumentIR*) : constructorDef `<typeArgumentIR*> `(# id_default* # id_optional*) -- if TYPEDEF nameIR_alias typeIR_alias = $find_typeDef_i(p, IC, prefixedNameIR) -- if (nameIR, typeArgumentIR*) = $resolve_type_alias(typeIR_alias) -- if _ : constructorDef # id_default* # id_optional* = $find_constructorDef_i(IC, ` nameIR, argumentIR*)
18.3.2. Resolving callables
When type checking, callables are resolved by:
Click to view the specification source
relation CallableType_ok: cursor typingContext |- callableTargetIR `<typeArgumentListIR> `(argumentIR*) : callableTypeIR `<# typeId*> `(# id* # id*)
At runtime, callables are resolved to callees by:
Click to view the specification source
relation Callee_eval: cursor evalContext arch |- callableTargetIR `<_> `(argumentListIR) : evalContext arch calleeResult
While resolving callees, exit; statements may be encountered. Thus, the
result of callee resolution is:
calleeResult
: continueResult<callee>
| abortResult
;
The following subsections describe the two phases, for each kind of callable.
18.3.2.1. Actions
During type checking, actions are resolved by:
actionTypeIR
: annotationList ACTION nameIR `( parameterIR* )
;
Click to view the specification source
rulegroup CallableType_ok/actionTypeIR: rule CallableType_ok/actionTypeIR: p TC |- prefixedNameIR `<eps> `(argumentIR*) : actionTypeIR `<# eps> `(# id_default* # id_optional*) -- if callableId : actionTypeIR # id_default* # id_optional* = $find_callableDef_overloaded_t(p, TC, prefixedNameIR, argumentIR*) -- CallableType_wf: $bound(p, TC) |- actionTypeIR
During runtime evaluation, actions are resolved by:
actionCallee
: ACTION cursor . nameIR `( parameterListIR # id* ) blockStatementIR
;
Click to view the specification source
rulegroup Callee_eval/actionCallee: rule Callee_eval/actionCallee: p EC ARCH |- referenceExpressionIR `<_> `(argumentListIR) : EC ARCH (` actionCallee) -- if _ : (p_ref, actionDef) # id_default* # _ = $find_callableDef_overloaded_e(p, EC, referenceExpressionIR, argumentListIR) -- if ACTION nameIR `(parameterListIR) blockStatementIR = actionDef -- if actionCallee = ACTION p_ref . nameIR `(parameterListIR # id_default*) blockStatementIR
18.3.2.2. Functions
During type checking, functions are resolved by:
functionTypeIR
: definedFunctionTypeIR
| externFunctionTypeIR
;
Click to view the specification source
rulegroup CallableType_ok/functionTypeIR:
rule CallableType_ok/functionTypeIR:
p TC |- prefixedNameIR `<typeArgumentIR*> `(argumentIR*) : functionTypeIR `<# typeId_impl*> `(# id_default* # id_optional*)
-- if callableId : functionTypeDefIR # id_default* # id_optional* = $find_callableDef_overloaded_t(p, TC, prefixedNameIR, argumentIR*)
-- if (functionTypeIR, typeId_impl*) = $specialize_callableTypeDefIR(functionTypeDefIR, typeArgumentIR*)
-- if bound = $union_set<typeId>($bound(p, TC), `{typeId_impl*})
-- CallableType_wf: bound |- functionTypeIR
During runtime evaluation, functions are resolved by:
functionCallee
: definedFunctionCallee
| externFunctionCallee
;
For extern functions,
Click to view the specification source
rulegroup Callee_eval/externFunctionCallee: rule Callee_eval/externFunctionCallee: p EC ARCH |- referenceExpressionIR `<_> `(argumentListIR) : EC ARCH (` externFunctionCallee) -- if _ : (_, externFunctionDef) # id_default* # id_optional* = $find_callableDef_overloaded_e(p, EC, referenceExpressionIR, argumentListIR) -- if EXTERN_FUNCTION nameIR `<typeParameterListIR> `(parameterListIR) = externFunctionDef -- if externFunctionCallee = EXTERN_FUNCTION nameIR `<typeParameterListIR> `(parameterListIR # id_default* # id_optional*)
For user-defined functions,
Click to view the specification source
rulegroup Callee_eval/definedFunctionCallee: rule Callee_eval/definedFunctionCallee: p EC ARCH |- referenceExpressionIR `<_> `(argumentListIR) : EC ARCH (` definedFunctionCallee) -- if _ : (_, definedFunctionDef) # id_default* # _ = $find_callableDef_overloaded_e(p, EC, referenceExpressionIR, argumentListIR) -- if FUNCTION nameIR `<typeParameterListIR> `(parameterListIR) blockStatementIR = definedFunctionDef -- if definedFunctionCallee = FUNCTION nameIR `<typeParameterListIR> `(parameterListIR # id_default*) blockStatementIR
18.3.2.3. Built-in methods
During type checking, built-in methods are resolved by:
builtinMethodTypeIR
: BUILTIN_METHOD nameIR `( parameterIR* ) : typeIR
;
Click to view the specification source
rulegroup CallableType_ok/builtinMethodTypeIR:
rule CallableType_ok/minmax-SizeIn-BitsBytes:
p TC |- (typedExpressionIR_base . nameIR) `<eps> `(eps) : methodTypeIR `<# eps> `(# eps # eps)
-- if nameIR <- ["minSizeInBits", "minSizeInBytes", "maxSizeInBits", "maxSizeInBytes"]
-- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base)
-- if typeIR_base_unroll = $unroll_typeIR(typeIR_base)
-- if ~(typeIR_base_unroll <: objectTypeIR)
-- if ~(typeIR_base_unroll <: synthesizedTypeIR)
-- if methodTypeIR = BUILTIN_METHOD nameIR `(eps) : INT
rule CallableType_ok/stack-push-pop-front:
p TC |- (typedExpressionIR_base . nameIR) `<eps> `(argumentIR) : methodTypeIR `<# eps> `(# eps # eps)
-- if nameIR <- ["push_front", "pop_front"]
-- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base)
-- if typeIR `[_] = $unroll_typeIR(typeIR_base)
-- if parameterIR = `EMPTY `EMPTY INT "count" eps
-- if methodTypeIR = BUILTIN_METHOD nameIR `(parameterIR) : INT
rule CallableType_ok/header-isValid:
p TC |- (typedExpressionIR_base . nameIR) `<eps> `(eps) : methodTypeIR `<# eps> `(# eps # eps)
-- if nameIR <- ["isValid"]
-- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base)
-- if HEADER _ `<_> `{_} = $unroll_typeIR(typeIR_base)
-- if methodTypeIR = BUILTIN_METHOD nameIR `(eps) : BOOL
rule CallableType_ok/header-union-isValid:
p TC |- (typedExpressionIR_base . nameIR) `<eps> `(eps) : methodTypeIR `<# eps> `(# eps # eps)
-- if nameIR <- ["isValid"]
-- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base)
-- if HEADER_UNION _ `<_> `{_} = $unroll_typeIR(typeIR_base)
-- if methodTypeIR = BUILTIN_METHOD nameIR `(eps) : BOOL
rule CallableType_ok/header-set-ValidInvalid:
p TC |- (typedExpressionIR_base . nameIR) `<eps> `(eps) : methodTypeIR `<# eps> `(# eps # eps)
-- if nameIR <- ["setValid", "setInvalid"]
-- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base)
-- if HEADER _ `<_> `{_} = $unroll_typeIR(typeIR_base)
-- if methodTypeIR = BUILTIN_METHOD nameIR `(eps) : VOID
During runtime evaluation, built-in methods are resolved by:
builtinMethodCallee
: BUILTIN_METHOD storageReference . nameIR `< typeArgumentListIR >
`( parameterListIR )
;
Click to view the specification source
rulegroup Callee_eval/builtinMethodCallee: rule Callee_eval/abort: p EC_0 ARCH_0 |- (typedExpressionIR . nameIR) `<_> `(argumentListIR) : EC_1 ARCH_1 abortResult -- if typedLvalueIR = $typedExpressionIR_as_typedLvalueIR(typedExpressionIR) -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 abortResult rule Callee_eval/header-stack-push_front: p EC_0 ARCH_0 |- (typedExpressionIR . "push_front") `<_> `(argumentIR) : EC_1 ARCH_1 (` builtinMethodCallee) -- if typedLvalueIR = $typedExpressionIR_as_typedLvalueIR(typedExpressionIR) -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 (` storageReference) -- if parameterIR = `EMPTY `EMPTY INT "count" eps -- if builtinMethodCallee = BUILTIN_METHOD storageReference . "push_front" `<eps> `(parameterIR) rule Callee_eval/header-stack-pop_front: p EC_0 ARCH_0 |- (typedExpressionIR . "pop_front") `<_> `(argumentIR) : EC_1 ARCH_1 (` builtinMethodCallee) -- if typedLvalueIR = $typedExpressionIR_as_typedLvalueIR(typedExpressionIR) -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 (` storageReference) -- if parameterIR = `EMPTY `EMPTY INT "count" eps -- if builtinMethodCallee = BUILTIN_METHOD storageReference . "pop_front" `<eps> `(parameterIR) rule Callee_eval/header-isValid: p EC_0 ARCH_0 |- (typedExpressionIR . "isValid") `<_> `(eps) : EC_1 ARCH_1 (` builtinMethodCallee) -- if typedLvalueIR = $typedExpressionIR_as_typedLvalueIR(typedExpressionIR) -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 (` storageReference) -- if builtinMethodCallee = BUILTIN_METHOD storageReference . "isValid" `<eps> `(eps) rule Callee_eval/header-setValid: p EC_0 ARCH_0 |- (typedExpressionIR . "setValid") `<_> `(eps) : EC_1 ARCH_1 (` builtinMethodCallee) -- if typedLvalueIR = $typedExpressionIR_as_typedLvalueIR(typedExpressionIR) -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 (` storageReference) -- if builtinMethodCallee = BUILTIN_METHOD storageReference . "setValid" `<eps> `(eps) rule Callee_eval/header-setInvalid: p EC_0 ARCH_0 |- (typedExpressionIR . "setInvalid") `<_> `(eps) : EC_1 ARCH_1 (` builtinMethodCallee) -- if typedLvalueIR = $typedExpressionIR_as_typedLvalueIR(typedExpressionIR) -- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 (` storageReference) -- if builtinMethodCallee = BUILTIN_METHOD storageReference . "setInvalid" `<eps> `(eps)
18.3.2.4. Extern methods
During type checking, extern methods are resolved by:
externMethodTypeIR
: EXTERN_METHOD nameIR `( parameterIR* ) : typeIR
| EXTERN_METHOD ABSTRACT nameIR `( parameterIR* ) : typeIR
;
Click to view the specification source
rulegroup CallableType_ok/externMethodTypeIR:
rule CallableType_ok/externMethodTypeIR:
p TC |- (typedExpressionIR_base . nameIR) `<typeArgumentIR*> `(argumentIR*) : externMethodTypeIR `<# typeId_impl*> `(# id_default* # id_optional*)
-- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base)
-- if EXTERN typeId `<_> `{(callableId_extern : externMethodTypeDefIR_extern)*} = $unroll_typeIR(typeIR_base)
-- if callTargetKey = $callTargetKey(nameIR, argumentIR*)
-- if _ : externMethodTypeDefIR # id_default* # id_optional* = $find_overloaded<externMethodTypeDefIR>(`{(callableId_extern : externMethodTypeDefIR_extern)*}, callTargetKey, $parameterListIR_of_externMethodTypeDefIR)
-- if (externMethodTypeIR, typeId_impl*) = $specialize_callableTypeDefIR(externMethodTypeDefIR, typeArgumentIR*)
-- if bound = $union_set<typeId>($bound(p, TC), `{typeId_impl*})
-- CallableType_wf: bound |- externMethodTypeIR
During runtime evaluation, extern methods are resolved by:
externMethodCallee
: EXTERN_METHOD objectId . nameIR `< typeParameterListIR >
`( parameterListIR # id* # id* ) `{ theta ; frame ; blockStatementIR? }
;
Click to view the specification source
rulegroup Callee_eval/externMethodCallee:
rule Callee_eval/abort:
p EC_0 ARCH_0 |- (typedExpressionIR . nameIR) `<_> `(argumentListIR) : EC_1 ARCH_1 abortResult
-- if typedLvalueIR = $typedExpressionIR_as_typedLvalueIR(typedExpressionIR)
-- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 abortResult
rule Callee_eval/cont:
p EC_0 ARCH_0 |- (typedExpressionIR . nameIR) `<_> `(argumentListIR) : EC_1 ARCH_1 (` externMethodCallee)
-- if typedLvalueIR = $typedExpressionIR_as_typedLvalueIR(typedExpressionIR)
-- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 (` storageReference)
-- Lvalue_read: p EC_1 ARCH_1 |- storageReference : (REF objectId)
-- if EXTERN _ `<theta> `{_ frame externMethodDefEnv} = $find_object_e(ARCH_1, objectId)
-- if callTargetKey = $callTargetKey(nameIR, argumentListIR)
-- if _ : externMethodDef # id_default* # id_optional* = $find_overloaded<externMethodDef>(externMethodDefEnv, callTargetKey, $parameterListIR_of_externMethodDef)
-- if EXTERN_METHOD nameIR `<typeParameterListIR> `(parameterListIR) blockStatementIR? = externMethodDef
-- if externMethodCallee = EXTERN_METHOD objectId . nameIR `<typeParameterListIR> `(parameterListIR # id_default* # id_optional*) `{theta ; frame ; blockStatementIR?}
18.3.2.5. Parser apply methods
During type checking, parser apply methods are resolved by:
parserApplyMethodTypeIR
: PARSER_APPLY `( parameterIR* )
;
Click to view the specification source
rulegroup CallableType_ok/parserApplyMethodTypeIR:
rule CallableType_ok/parserApplyMethodTypeIR:
p TC |- (typedExpressionIR_base . "apply") `<eps> `(argumentIR*) : parserApplyMethodTypeIR `<# eps> `(# id_default* # id_optional*)
-- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base)
-- if PARSER _ `<_> `(parameterIR*) = $unroll_typeIR(typeIR_base)
-- if parserApplyMethodTypeIR = PARSER_APPLY `(parameterIR*)
-- (if (_ _ _ id_param _ = parameterIR))*
-- if callableId = "apply" `(id_param*)
-- if callTargetKey = $callTargetKey("apply", argumentIR*)
-- if _ : _ # id_default* # id_optional* = $find_overloaded<parserApplyMethodTypeIR>(`{(callableId : parserApplyMethodTypeIR)}, callTargetKey, $parameterListIR_of_parserApplyMethodTypeIR)
During runtime evaluation, parser apply methods are resolved by:
parserApplyMethodCallee
: PARSER objectId . APPLY `( parameterListIR # id* )
`{ theta ; frame ; parserLocalDeclarationListIR ; stateEnv }
;
Click to view the specification source
rulegroup Callee_eval/parserApplyMethodCallee:
rule Callee_eval/abort:
p EC_0 ARCH_0 |- (typedExpressionIR . "apply") `<_> `(argumentListIR) : EC_1 ARCH_1 abortResult
-- if typedLvalueIR = $typedExpressionIR_as_typedLvalueIR(typedExpressionIR)
-- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 abortResult
rule Callee_eval/cont:
p EC_0 ARCH_0 |- (typedExpressionIR . "apply") `<_> `(argumentListIR) : EC_1 ARCH_1 (` parserApplyMethodCallee)
-- if typedLvalueIR = $typedExpressionIR_as_typedLvalueIR(typedExpressionIR)
-- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 (` storageReference)
-- Lvalue_read: p EC_1 ARCH_1 |- storageReference : (REF objectId)
-- if PARSER `<theta> `(parameterListIR) `{frame parserLocalDeclarationListIR stateEnv} = $find_object_e(ARCH_1, objectId)
-- if callableId = $callableId_IR("apply", parameterListIR)
-- if parserApplyMethodDef = PARSER_APPLY `(parameterListIR) `{parserLocalDeclarationListIR ; stateEnv}
-- if callTargetKey = $callTargetKey("apply", argumentListIR)
-- if _ : _ # id_default* # _ = $find_overloaded<parserApplyMethodDef>(`{(callableId : parserApplyMethodDef)}, callTargetKey, $parameterListIR_of_parserApplyMethodDef)
-- if parserApplyMethodCallee = PARSER objectId . APPLY `(parameterListIR # id_default*) `{theta ; frame ; parserLocalDeclarationListIR ; stateEnv}
18.3.2.6. Control apply methods
During type checking, control apply methods are resolved by:
controlApplyMethodTypeIR
: CONTROL_APPLY `( parameterIR* )
;
Click to view the specification source
rulegroup CallableType_ok/controlApplyMethodTypeIR:
rule CallableType_ok/controlApplyMethodTypeIR:
p TC |- (typedExpressionIR_base . "apply") `<eps> `(argumentIR*) : controlApplyMethodTypeIR `<# eps> `(# id_default* # id_optional*)
-- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base)
-- if CONTROL _ `<_> `(parameterIR*) = $unroll_typeIR(typeIR_base)
-- if controlApplyMethodTypeIR = CONTROL_APPLY `(parameterIR*)
-- (if (_ _ _ id_param _ = parameterIR))*
-- if callableId = "apply" `(id_param*)
-- if callTargetKey = $callTargetKey("apply", argumentIR*)
-- if _ : _ # id_default* # id_optional* = $find_overloaded<controlApplyMethodTypeDefIR>(`{(callableId : controlApplyMethodTypeIR)}, callTargetKey, $parameterListIR_of_controlApplyMethodTypeIR)
During runtime evaluation, control apply methods are resolved by:
controlApplyMethodCallee
: CONTROL objectId . APPLY `( parameterListIR # id* )
`{ theta ; frame ; controlLocalDeclarationListIR ; actionDefEnv ; controlBodyIR }
;
Click to view the specification source
rulegroup Callee_eval/controlApplyMethodCallee:
rule Callee_eval/abort:
p EC_0 ARCH_0 |- (typedExpressionIR . "apply") `<_> `(argumentListIR) : EC_1 ARCH_1 abortResult
-- if typedLvalueIR = $typedExpressionIR_as_typedLvalueIR(typedExpressionIR)
-- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 abortResult
rule Callee_eval/cont:
p EC_0 ARCH_0 |- (typedExpressionIR . "apply") `<_> `(argumentListIR) : EC_1 ARCH_1 (` controlApplyMethodCallee)
-- if typedLvalueIR = $typedExpressionIR_as_typedLvalueIR(typedExpressionIR)
-- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 (` storageReference)
-- Lvalue_read: p EC_1 ARCH_1 |- storageReference : (REF objectId)
-- if CONTROL `<theta> `(parameterListIR) `{frame controlLocalDeclarationListIR actionDefEnv controlBodyIR} = $find_object_e(ARCH_1, objectId)
-- if callableId = $callableId_IR("apply", parameterListIR)
-- if controlApplyMethodDef = CONTROL_APPLY `(parameterListIR) `{controlLocalDeclarationListIR ; actionDefEnv ; controlBodyIR}
-- if callTargetKey = $callTargetKey("apply", argumentListIR)
-- if _ : _ # id_default* # _ = $find_overloaded<controlApplyMethodDef>(`{(callableId : controlApplyMethodDef)}, callTargetKey, $parameterListIR_of_controlApplyMethodDef)
-- if controlApplyMethodCallee = CONTROL objectId . APPLY `(parameterListIR # id_default*) `{theta ; frame ; controlLocalDeclarationListIR ; actionDefEnv ; controlBodyIR}
18.3.2.7. Table apply methods
During type checking, table apply methods are resolved by:
tableApplyMethodTypeIR
: TABLE_APPLY : tableMetadataStructTypeIR
;
Click to view the specification source
rulegroup CallableType_ok/tableApplyMethodTypeIR:
rule CallableType_ok/tableApplyMethodTypeIR:
p TC |- (typedExpressionIR_base . "apply") `<eps> `(eps) : tableApplyMethodTypeIR `<# eps> `(# eps # eps)
-- if typeIR_base = $type_of_typedExpressionIR(typedExpressionIR_base)
-- if TABLE _ `{tableMetadataStructTypeIR} = $unroll_typeIR(typeIR_base)
-- if tableApplyMethodTypeIR = TABLE_APPLY : tableMetadataStructTypeIR
During runtime evaluation, table apply methods are resolved by:
tableApplyMethodCallee
: TABLE objectId . APPLY `{ frame ; tableObjectProperty }
;
Click to view the specification source
rulegroup Callee_eval/tableApplyMethodCallee:
rule Callee_eval/abort:
p EC_0 ARCH_0 |- (typedExpressionIR . "apply") `<_> `(eps) : EC_1 ARCH_1 abortResult
-- if typedLvalueIR = $typedExpressionIR_as_typedLvalueIR(typedExpressionIR)
-- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 abortResult
rule Callee_eval/cont:
p EC_0 ARCH_0 |- (typedExpressionIR . "apply") `<_> `(eps) : EC_1 ARCH_1 (` tableApplyMethodCallee)
-- if typedLvalueIR = $typedExpressionIR_as_typedLvalueIR(typedExpressionIR)
-- Lvalue_eval: p EC_0 ARCH_0 |- typedLvalueIR : EC_1 ARCH_1 (` storageReference)
-- Lvalue_read: p EC_1 ARCH_1 |- storageReference : (REF objectId)
-- if TABLE typeId `{frame TBL} = $find_object_e(ARCH_1, objectId)
-- if tableApplyMethodCallee = TABLE objectId . APPLY `{frame ; TBL}
18.4. Call convention
18.5. Runtime evaluation of constructor calls
18.6. Runtime evaluation of callable calls
At runtime, callable callees are evaluated as follows:
Click to view the specification source
relation Call_eval: cursor evalContext arch |- callee @ `<typeArgumentListIR> `(argumentListIR) : evalContext arch callResult
The following subsections describe the evaluation of callable calls for different types of callees.
18.6.1. Actions
An action call is evaluated as follows:
Click to view the specification source
rulegroup Call_eval/actionCallee: rule Call_eval/copyin-exit: p EC_0 ARCH_0 |- actionCallee @ `<eps> `(argumentListIR) : EC_1 ARCH_1 EXIT -- if ACTION p_shared . nameIR `(parameterIR* # id_default*) blockStatementIR = actionCallee -- if EC_callee_0 = $inherit_e(p_shared, EC_0) -- if GIVEN parameterIR_aligned* DEFAULT parameterIR_default* = $align_parameterListIR(parameterIR*, argumentListIR, id_default*, eps) -- Copy_in: p_shared ARCH_0 |- p EC_0 parameterIR_aligned* @ LOCAL EC_callee_0 argumentListIR ~> ARCH_1 EC_1 EC_callee_1 # EXIT rule Call_eval/copyin-cont-ActionBody-exit: p EC_0 ARCH_0 |- actionCallee @ `<eps> `(argumentListIR) : EC_2 ARCH_2 EXIT -- if ACTION p_shared . nameIR `(parameterIR* # id_default*) blockStatementIR = actionCallee -- if EC_callee_0 = $inherit_e(p_shared, EC_0) -- if GIVEN parameterIR_aligned* DEFAULT parameterIR_default* = $align_parameterListIR(parameterIR*, argumentListIR, id_default*, eps) -- Copy_in: p_shared ARCH_0 |- p EC_0 parameterIR_aligned* @ LOCAL EC_callee_0 argumentListIR ~> ARCH_1 EC_1 EC_callee_1 # (` storageReference?*) -- Copy_in_default: parameterIR_default* @ LOCAL EC_callee_1 ~> EC_callee_2 -- ActionBody_eval: EC_callee_2 ARCH_1 |- blockStatementIR : EC_callee_3 ARCH_2 EXIT -- Copy_out: p_shared ARCH_2 |- p EC_1 parameterIR_aligned* @ LOCAL EC_callee_3 storageReference?* ~> EC_2 rule Call_eval/return: p EC_0 ARCH_0 |- actionCallee @ `<eps> `(argumentListIR) : EC_2 ARCH_2 (RETURN eps) -- if ACTION p_shared . nameIR `(parameterIR* # id_default*) blockStatementIR = actionCallee -- if EC_callee_0 = $inherit_e(p_shared, EC_0) -- if GIVEN parameterIR_aligned* DEFAULT parameterIR_default* = $align_parameterListIR(parameterIR*, argumentListIR, id_default*, eps) -- Copy_in: p_shared ARCH_0 |- p EC_0 parameterIR_aligned* @ LOCAL EC_callee_0 argumentListIR ~> ARCH_1 EC_1 EC_callee_1 # (` storageReference?*) -- Copy_in_default: parameterIR_default* @ LOCAL EC_callee_1 ~> EC_callee_2 -- ActionBody_eval: EC_callee_2 ARCH_1 |- blockStatementIR : EC_callee_3 ARCH_2 (RETURN eps) -- Copy_out: p_shared ARCH_2 |- p EC_1 parameterIR_aligned* @ LOCAL EC_callee_3 storageReference?* ~> EC_2
An action body is evaluated as follows:
Click to view the specification source
relation ActionBody_eval: evalContext arch |- blockStatementIR : evalContext arch actionResult
The result of evaluating an action body is either an exit or a return.
${sytnax: actionResult}
Click to view the specification source
rulegroup ActionBody_eval: rule ActionBody_eval/cont: EC_0 ARCH_0 |- blockStatementIR : EC_1 ARCH_1 (RETURN eps) -- Block_eval: EC_0 ARCH_0 |- blockStatementIR : EC_1 ARCH_1 `EMPTY rule ActionBody_eval/exit: EC_0 ARCH_0 |- blockStatementIR : EC_1 ARCH_1 EXIT -- Block_eval: EC_0 ARCH_0 |- blockStatementIR : EC_1 ARCH_1 EXIT rule ActionBody_eval/return-void: EC_0 ARCH_0 |- blockStatementIR : EC_1 ARCH_1 (RETURN eps) -- Block_eval: EC_0 ARCH_0 |- blockStatementIR : EC_1 ARCH_1 (RETURN eps)
18.6.2. Functions
18.6.3. Built-in methods
18.6.4. Extern methods
18.6.5. Parser apply methods
18.6.6. Control apply methods
18.6.7. Table apply methods
19. Operations
19.1. Unary operations
Unary operations are evaluated with:
Click to view the specification source
def $un_op(~, value) = $un_bnot(value) def $un_op(!, value) = $un_lnot(value) def $un_op(+, value) = $un_plus(value) def $un_op(-, value) = $un_minus(value)
Negation
Click to view the specification source
def $un_lnot(`B b) = `B (~b)
Bitwise complement
Click to view the specification source
def $un_bnot(w W i) = w W i'' -- if i' = $bneg(i) -- if i'' = $ite<int>(i' >= 0, i', $pow2(w) + i')
Plus
Click to view the specification source
def $un_plus(D i) = D i def $un_plus(w W i) = w W i def $un_plus(w S i) = w S i
Minus
Click to view the specification source
def $un_minus(D i) = D -i def $un_minus(w W i) = w W i' -- if i' = $pow2(w) - i def $un_minus(w S i) = w S i' -- if i' = $int_to_bitstr(w, $bitstr_to_int(w, -i))
19.2. Binary operations
Binary operations are evaluated with:
Click to view the specification source
def $bin_op(+, value_l, value_r) = $bin_plus(value_l, value_r) def $bin_op(|+|, value_l, value_r) = $bin_satplus(value_l, value_r) def $bin_op(-, value_l, value_r) = $bin_minus(value_l, value_r) def $bin_op(|-|, value_l, value_r) = $bin_satminus(value_l, value_r) def $bin_op(*, value_l, value_r) = $bin_mul(value_l, value_r) def $bin_op(/, value_l, value_r) = $bin_div(value_l, value_r) def $bin_op(%, value_l, value_r) = $bin_mod(value_l, value_r) def $bin_op(<<, value_l, value_r) = $bin_shl(value_l, value_r) def $bin_op(>>, value_l, value_r) = $bin_shr(value_l, value_r) def $bin_op(<=, value_l, value_r) = `B $bin_le(value_l, value_r) def $bin_op(>=, value_l, value_r) = `B $bin_ge(value_l, value_r) def $bin_op(<, value_l, value_r) = `B $bin_lt(value_l, value_r) def $bin_op(>, value_l, value_r) = `B $bin_gt(value_l, value_r) def $bin_op(==, value_l, value_r) = `B $bin_eq(value_l, value_r) def $bin_op(!=, value_l, value_r) = `B $bin_ne(value_l, value_r) def $bin_op(&, value_l, value_r) = $bin_band(value_l, value_r) def $bin_op(^, value_l, value_r) = $bin_bxor(value_l, value_r) def $bin_op(|, value_l, value_r) = $bin_bor(value_l, value_r) def $bin_op(++, value_l, value_r) = $bin_concat(value_l, value_r)
Addition
Click to view the specification source
def $bin_plus(D i_l, D i_r) = D i_l + i_r def $bin_plus(w W i_l, w W i_r) = w W i' -- if i_l' = $bitstr_to_int(w, i_l) -- if i_r' = $bitstr_to_int(w, i_r) -- if i' = $int_to_bitstr(w, i_l' + i_r') def $bin_plus(w S i_l, w S i_r) = w S i' -- if i_l' = $bitstr_to_int(w, i_l) -- if i_r' = $bitstr_to_int(w, i_r) -- if i' = $int_to_bitstr(w, i_l' + i_r')
Saturated addition
Click to view the specification source
def $bin_satplus(w W i_l, w W i_r) = w W i' -- if i = i_l + i_r -- if i_max = $pow2(w) -- if i' = $ite<int>(i < i_max, i, i_max - 1) def $bin_satplus(w S i_l, w S i_r) = w S i''' -- if i_l' = $bitstr_to_int(w, i_l) -- if i_r' = $bitstr_to_int(w, i_r) -- if i' = i_l' + i_r' -- if i' > 0 -- if w' = w - 1 -- if i_max = $pow2(w') -- if i'' = $ite<int>(i' < i_max, i', i_max - 1) -- if i''' = $int_to_bitstr(w, i'') def $bin_satplus(w S i_l, w S i_r) = w S i''' -- if i_l' = $bitstr_to_int(w, i_l) -- if i_r' = $bitstr_to_int(w, i_r) -- if i' = i_l' + i_r' -- if i' <= 0 -- if w' = w - 1 -- if i_min = -$pow2(w') -- if i'' = $ite<int>(i' >= i_min, i', i_min) -- if i''' = $int_to_bitstr(w, i'')
Subtraction
Click to view the specification source
def $bin_minus(D i_l, D i_r) = D i_l - i_r def $bin_minus(w W i_l, w W i_r) = w W i' -- if i_l' = $bitstr_to_int(w, i_l) -- if i_r' = $bitstr_to_int(w, i_r) -- if i' = $int_to_bitstr(w, i_l' - i_r') def $bin_minus(w S i_l, w S i_r) = w S i' -- if i_l' = $bitstr_to_int(w, i_l) -- if i_r' = $bitstr_to_int(w, i_r) -- if i' = $int_to_bitstr(w, i_l' - i_r')
Saturated subtraction
Click to view the specification source
def $bin_satminus(w W i_l, w W i_r) = w W i' -- if i = i_l - i_r -- if i' = $ite<int>(i >= 0, i, 0) def $bin_satminus(w S i_l, w S i_r) = w S i''' -- if i_l' = $bitstr_to_int(w, i_l) -- if i_r' = $bitstr_to_int(w, i_r) -- if i' = i_l' - i_r' -- if i' > 0 -- if w' = w - 1 -- if i_max = $pow2(w') -- if i'' = $ite<int>(i' < i_max, i', i_max - 1) -- if i''' = $int_to_bitstr(w, i'') def $bin_satminus(w S i_l, w S i_r) = w S i''' -- if i_l' = $bitstr_to_int(w, i_l) -- if i_r' = $bitstr_to_int(w, i_r) -- if i' = i_l' - i_r' -- if i' <= 0 -- if w' = w - 1 -- if i_min = -$pow2(w') -- if i'' = $ite<int>(i' >= i_min, i', i_min) -- if i''' = $int_to_bitstr(w, i'')
Multiplication
Click to view the specification source
def $bin_mul(D i_l, D i_r) = D i_l * i_r def $bin_mul(w W i_l, w W i_r) = w W i' -- if i_l' = $bitstr_to_int(w, i_l) -- if i_r' = $bitstr_to_int(w, i_r) -- if i' = $int_to_bitstr(w, i_l' * i_r') def $bin_mul(w S i_l, w S i_r) = w S i' -- if i_l' = $bitstr_to_int(w, i_l) -- if i_r' = $bitstr_to_int(w, i_r) -- if i' = $int_to_bitstr(w, i_l' * i_r')
Division
Click to view the specification source
def $bin_div(D i_l, D i_r) = D i_l / i_r
Modulus
Click to view the specification source
def $bin_mod(D i_l, D i_r) = D i_l \ i_r
Shift left
Click to view the specification source
def $bin_shl(D i_l, D i_r) = D $shl(i_l, i_r) def $bin_shl(D i_l, w_r W i_r) = D $shl(i_l, i_r) def $bin_shl(D i_l, w_r S i_r) = D $shl(i_l, i_r') -- if i_r' = $bitstr_to_int(w_r, i_r) def $bin_shl(w_l W i_l, D i_r) = $bin_shl(w_l W i_l, w_l W $bitstr_to_int(w_l, i_r)) def $bin_shl(w_l W i_l, w_r W i_r) = w_l W i' -- if i' = $int_to_bitstr(w_l, $shl(i_l, i_r)) def $bin_shl(w_l W i_l, w_r S i_r) = w_l W i' -- if i_r' = $bitstr_to_int(w_r, i_r) -- if i' = $int_to_bitstr(w_l, $shl(i_l, i_r')) def $bin_shl(w_l S i_l, D i_r) = $bin_shl(w_l S i_l, w_l S $bitstr_to_int(w_l, i_r)) def $bin_shl(w_l S i_l, w_r W i_r) = w_l S i' -- if i_l' = $bitstr_to_int(w_l, i_l) -- if i' = $int_to_bitstr(w_l, $shl(i_l', i_r)) def $bin_shl(w_l S i_l, w_r S i_r) = w_l S i' -- if i_l' = $bitstr_to_int(w_l, i_l) -- if i_r' = $bitstr_to_int(w_r, i_r) -- if i' = $int_to_bitstr(w_l, $shl(i_l', i_r'))
Shift right
Click to view the specification source
def $bin_shr(D i_l, D i_r) = D $shr(i_l, i_r) def $bin_shr(D i_l, w_r W i_r) = D $shr(i_l, i_r) def $bin_shr(D i_l, w_r S i_r) = D $shr(i_l, i_r') -- if i_r' = $bitstr_to_int(w_r, i_r) def $bin_shr(w_l W i_l, D i_r) = $bin_shr(w_l W i_l, w_l W $bitstr_to_int(w_l, i_r)) def $bin_shr(w_l W i_l, w_r W i_r) = w_l W i' -- if i' = $int_to_bitstr(w_l, $shr(i_l, i_r)) def $bin_shr(w_l W i_l, w_r S i_r) = w_l W i' -- if i_r' = $bitstr_to_int(w_r, i_r) -- if i' = $int_to_bitstr(w_l, $shr(i_l, i_r')) def $bin_shr(w_l S i_l, D i_r) = w_l S i'' -- if i_l' = $bitstr_to_int(w_l, i_l) -- if i_l' < 0 -- if w_l' = w_l - 1 -- if i' = $shr_arith(i_l, i_r, $pow2(w_l')) -- if i'' = $int_to_bitstr(w_l, i') def $bin_shr(w_l S i_l, D i_r) = w_l S i'' -- if i_l' = $bitstr_to_int(w_l, i_l) -- if i_l' >= 0 -- if i' = $shr(i_l, i_r) -- if i'' = $int_to_bitstr(w_l, i') def $bin_shr(w_l S i_l, w_r W i_r) = w_l S i'' -- if i_l' = $bitstr_to_int(w_l, i_l) -- if i_l' < 0 -- if w_l' = w_l - 1 -- if i' = $shr_arith(i_l, i_r, $pow2(w_l')) -- if i'' = $int_to_bitstr(w_l, i') def $bin_shr(w_l S i_l, w_r W i_r) = w_l S i'' -- if i_l' = $bitstr_to_int(w_l, i_l) -- if i_l' >= 0 -- if i' = $shr(i_l, i_r) -- if i'' = $int_to_bitstr(w_l, i') def $bin_shr(w_l S i_l, w_r S i_r) = w_l S i'' -- if i_l' = $bitstr_to_int(w_l, i_l) -- if i_r' = $bitstr_to_int(w_r, i_r) -- if i_l' < 0 -- if w_l' = w_l - 1 -- if i' = $shr_arith(i_l, i_r, $pow2(w_l')) -- if i'' = $int_to_bitstr(w_l, i') def $bin_shr(w_l S i_l, w_r S i_r) = w_l S i'' -- if i_l' = $bitstr_to_int(w_l, i_l) -- if i_r' = $bitstr_to_int(w_r, i_r) -- if i_l' >= 0 -- if i' = $shr(i_l, i_r) -- if i'' = $int_to_bitstr(w_l, i')
Equality and inequality
Click to view the specification source
def $bin_eq(`B b_a, `B b_b) = (b_a = b_b)
def $bin_eq(ERROR . id_a, ERROR . id_b) = (id_a = id_b)
def $bin_eq(MATCH_KIND . id_a, MATCH_KIND . id_b) = (id_a = id_b)
def $bin_eq(stringValue_a, stringValue_b) = (stringValue_a = stringValue_b)
def $bin_eq(D i_a, D i_b) = (i_a = i_b)
def $bin_eq(w_a W i_a, w_b W i_b) = (w_a = w_b) /\ (i_a = i_b)
def $bin_eq(w_a S i_a, w_b S i_b) = (w_a = w_b) /\ (i_a = i_b)
def $bin_eq(w_max_a . _ V i_a, w_max_b . _ V i_b) = (w_max_a = w_max_b) /\ (i_a = i_b)
def $bin_eq(LIST `[value_a*], LIST `[value_b*]) = |value_a*| = |value_b*| /\ $forall_($bin_eq(value_a, value_b)*)
def $bin_eq(TUPLE `(value_a*), TUPLE `(value_b*)) = |value_a*| = |value_b*| /\ $forall_($bin_eq(value_a, value_b)*)
def $bin_eq(HEADER_STACK `[value_a* `(_ ; n_size_a)], HEADER_STACK `[value_b* `(_ ; n_size_b)]) = |value_a*| = |value_b*| /\ $forall_($bin_eq(value_a, value_b)*) /\ (n_size_a = n_size_b)
def $bin_eq(STRUCT typeId_a `{fieldValue_a*}, STRUCT typeId_b `{fieldValue_b*}) = (typeId_a = typeId_b) /\ |fieldValue_a*| = |fieldValue_b*| /\ $forall_($bin_eq(value_field_a, value_field_b)*) /\ $forall_((nameIR_field_a = nameIR_field_b)*)
-- (if (value_field_a nameIR_field_a ; = fieldValue_a))*
-- (if (value_field_b nameIR_field_b ; = fieldValue_b))*
def $bin_eq(HEADER typeId_a `{_ ; fieldValue_a*}, HEADER typeId_b `{_ ; fieldValue_b*}) = (typeId_a = typeId_b) /\ |fieldValue_a*| = |fieldValue_b*| /\ $forall_($bin_eq(value_field_a, value_field_b)*) /\ $forall_((nameIR_field_a = nameIR_field_b)*)
-- (if (value_field_a nameIR_field_a ; = fieldValue_a))*
-- (if (value_field_b nameIR_field_b ; = fieldValue_b))*
def $bin_eq(HEADER_UNION typeId_a `{fieldValue_a*}, HEADER_UNION typeId_b `{fieldValue_b*}) = (typeId_a = typeId_b) /\ |fieldValue_a*| = |fieldValue_b*| /\ $forall_($bin_eq(value_field_a, value_field_b)*) /\ $forall_((nameIR_field_a = nameIR_field_b)*)
-- (if (value_field_a nameIR_field_a ; = fieldValue_a))*
-- (if (value_field_b nameIR_field_b ; = fieldValue_b))*
def $bin_eq(typeId_a . nameIR_field_a, typeId_b . nameIR_field_b) = (typeId_a = typeId_b) /\ (nameIR_field_a = nameIR_field_b)
def $bin_eq(typeId_a . _ . value_field_a, typeId_b . _ . value_field_b) = (typeId_a = typeId_b) /\ $bin_eq(value_field_a, value_field_b)
def $bin_eq({#}, {#}) = true
Click to view the specification source
def $bin_ne(value_l, value_r) = ~$bin_eq(value_l, value_r)
Comparisons
Click to view the specification source
def $bin_lt(D i_l, D i_r) = i_l < i_r def $bin_lt(w W i_l, w W i_r) = i_l < i_r def $bin_lt(w S i_l, w S i_r) = i_l' < i_r' -- if i_l' = $bitstr_to_int(w, i_l) -- if i_r' = $bitstr_to_int(w, i_r)
Click to view the specification source
def $bin_le(D i_l, D i_r) = i_l <= i_r def $bin_le(w W i_l, w W i_r) = i_l <= i_r def $bin_le(w S i_l, w S i_r) = i_l' <= i_r' -- if i_l' = $bitstr_to_int(w, i_l) -- if i_r' = $bitstr_to_int(w, i_r)
Click to view the specification source
def $bin_gt(D i_l, D i_r) = i_l > i_r def $bin_gt(w W i_l, w W i_r) = i_l > i_r def $bin_gt(w S i_l, w S i_r) = i_l' > i_r' -- if i_l' = $bitstr_to_int(w, i_l) -- if i_r' = $bitstr_to_int(w, i_r)
Click to view the specification source
def $bin_ge(D i_l, D i_r) = i_l >= i_r def $bin_ge(w W i_l, w W i_r) = i_l >= i_r def $bin_ge(w S i_l, w S i_r) = i_l' >= i_r' -- if i_l' = $bitstr_to_int(w, i_l) -- if i_r' = $bitstr_to_int(w, i_r)
Bitwise
Click to view the specification source
def $bin_band(w W i_l, w W i_r) = w W i' -- if i' = $int_to_bitstr(w, $band(i_l, i_r)) def $bin_band(w S i_l, w S i_r) = w S i' -- if i_l' = $bitstr_to_int(w, i_l) -- if i_r' = $bitstr_to_int(w, i_r) -- if i' = $int_to_bitstr(w, $band(i_l', i_r'))
Click to view the specification source
def $bin_bor(w W i_l, w W i_r) = w W i' -- if i' = $int_to_bitstr(w, $bor(i_l, i_r)) def $bin_bor(w S i_l, w S i_r) = w S i' -- if i_l' = $bitstr_to_int(w, i_l) -- if i_r' = $bitstr_to_int(w, i_r) -- if i' = $int_to_bitstr(w, $bor(i_l', i_r'))
Click to view the specification source
def $bin_bxor(w W i_l, w W i_r) = w W i' -- if i' = $int_to_bitstr(w, $bxor(i_l, i_r)) def $bin_bxor(w S i_l, w S i_r) = w S i' -- if i_l' = $bitstr_to_int(w, i_l) -- if i_r' = $bitstr_to_int(w, i_r) -- if i' = $int_to_bitstr(w, $bxor(i_l', i_r'))
19.3. Default values
Some P4 types define a default value, which can be used to automatically initialize values of that type. The high-level description of 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 incore.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, and package types.
Default values are derived from:
Click to view the specification source
dec $default(typeIR) : value
Click to view the specification source
def $default(BOOL) = `B false
def $default(ERROR) = ERROR . "NoError"
def $default(STRING) = " text_empty "
-- if text_empty = ""
def $default(INT) = D 0
def $default(BIT `<w>) = w W 0
def $default(INT `<w>) = w S 0
def $default(VARBIT `<w>) = w . 0 V 0
def $default(TYPEDEF _ typeIR) = $default(typeIR)
def $default(TYPE _ typeIR) = $default(typeIR)
def $default(TUPLE `<typeIR*>) = TUPLE `($default(typeIR)*)
def $default(typeIR `[n_s]) = HEADER_STACK `[value* `(0 ; n_s)]
-- if value* = $repeat_<value>($default(typeIR), n_s)
def $default(STRUCT typeId `<_> `{(_ typeIR_f id_f ;)*}) = STRUCT typeId `{($default(typeIR_f) id_f ;)*}
def $default(HEADER typeId `<_> `{(_ typeIR_f id_f ;)*}) = HEADER typeId `{false ; ($default(typeIR_f) id_f ;)*}
def $default(HEADER_UNION typeId `<_> `{(_ typeIR_f id_f ;)*}) = HEADER_UNION typeId `{($default(typeIR_f) id_f ;)*}
def $default(ENUM typeId `{id_f_h :: _}) = typeId . id_f_h
def $default(ENUM typeId `<typeIR> `{(nameIR_field = value_field ;)*}) = typeId . id_zero . value_zero
-- if value_zero = $cast_int(typeIR, D 0)
-- if id_zero = $assoc_<value, id>(value_zero, (value_field, nameIR_field)*)
def $default(ENUM typeId `<typeIR> `{(nameIR_field = value_field ;)*}) = typeId . id_zero . value_zero
-- if value_zero = $cast_int(typeIR, D 0)
-- if eps = $assoc_<value, id>(value_zero, (value_field, nameIR_field)*)
-- if id_zero = "__UNSPECIFIED"
20. Annotations
Annotations are a simple mechanism for extending the P4 language to some
limited degree without changing the grammar. Annotations are attached to types,
fields, variables, etc. using the @ syntax (as shown explicitly in the P4
grammar). Unstructured annotations, or just "annotations," have an optional
body; structured annotations have a mandatory body, containing at least a pair
of square brackets [].
annotationBody
: /* empty */
| annotationBody `( annotationBody )
| annotationBody annotationToken
;
structuredAnnotationBody
: sequenceOrRecordElementExpression trailingCommaOpt
;
annotation
: @ name
| @ name `( annotationBody )
| @ name `[ structuredAnnotationBody ]
| @ PRAGMA name annotationBody
;
Structured annotations and unstructured annotations on any one element must not
use the same name. Thus, a given name can only be applied to one type of
annotation or the other for any one element. An annotation used on one element
does not affect the annotation on another because they have different scope.
This is legal:
@my_anno(1) table T { /* body omitted */ }
@my_anno[2] table U { /* body omitted */ } // OK - different scope than previous
// use of my_anno
This is illegal:
@my_anno(1)
@my_anno[2] table U { /* body omitted */ } // Error - changed type of anno
// on an element
Multiple unstructured annotations using the same name can appear on a given
element; they are cumulative. Each one will be bound to that element. In
contrast, only one structured annotation using a given name may appear on an
element; multiple uses of the same name will produce an error.
This is legal:
@my_anno(1)
@my_anno(2) table U { /* body omitted */ } // OK - unstructured annos accumulate
This is illegal:
@my_anno[1]
@my_anno[2] table U { /* body omitted */ } // Error - reused the same structured
// anno on an element
20.1. Bodies of Unstructured Annotations
The flexibility of P4 unstructured annotations comes from the minimal structure
mandated by the P4 grammar: unstructured annotation bodies may contain any
sequence of terminals, so long as parentheses are balanced. In the following
grammar fragment, the annotationToken non-terminal represents any terminal
produced by the lexer, including keywords, identifiers, string and integer
literals, and symbols, but excluding parentheses.
annotationBody
: /* empty */
| annotationBody `( annotationBody )
| annotationBody annotationToken
;
Unstructured annotations may impose additional structure on their bodies, and
are not confined to the P4 language. For example, the P4Runtime specification
[2] defines a @pkginfo annotation that expects key-value
pairs.
=== Bodies of Structured Annotations
Unlike unstructured annotations, structured annotations use square brackets
[…] and have a restricted format. They are commonly used to declare custom
metadata, consisting of expression lists or key-value lists but not both. An
expressionList may be empty or contain a comma-separated list of member
expressions. A namedExpressionList consists of one or more
namedExpressions, each consisting of a key and a value expression. Note
the syntax for expression is rich, see Appendix E for details.
All expressions within a structuredAnnotationBody must be compile-time
known values with a result type that is either: string, int, or bool. In
particular, structured expressions (e.g. an expression containing an
expressionList, a namedExpressionList, etc.) are not allowed. Note that
P4Runtime information (P4Info) may stipulate additional restrictions. For
example, an integer expression might be limited to 64-bit values.
It is illegal to duplicate a name within the namedExpressionList of a
structured annotation.
structuredAnnotationBody
: sequenceOrRecordElementExpression trailingCommaOpt
;
sequenceOrRecordElementExpression
: sequenceElementExpression
| recordElementExpression
;
sequenceElementExpression = expressionList
recordElementExpression
: name = expression
| name = expression , ...
| name = expression , namedExpressionList
| name = expression , namedExpressionList , ...
;
namedExpression
: name = expression
;
20.1.1. Structured Annotation Examples
Empty Expression List
The following example produces an empty annotation:
@Empty[]
table t {
/* body omitted */
}
Mixed Expression List
The following example will produce an effective expression list as follows:
[1,"hello",true, false, 11]
#define TEXT_CONST "hello"
#define NUM_CONST 6
@MixedExprList[1,TEXT_CONST,true,1==2,5+NUM_CONST]
table t {
/* body omitted */
}
namedExprssionList of Strings
@Labels[short="Short Label", hover="My Longer Table Label to appear in hover-help"]
table t {
/* body omitted */
}
namedExpressionList of Mixed Expressions
The following example will produce an effective namedExpressionList as
follows.
[label="text", my_bool=true, int_val=6]
@MixedKV[label="text", my_bool=true, int_val=2*3]
table t {
/* body omitted */
}
Illegal Mixing of namedExpression and expressionList
The following example is invalid because the body contains both a
namedExpression and an expression:
@IllegalMixing[key=4, 5] // illegal mixing
table t {
/* body omitted */
}
Illegal Duplicate Key
The following example is invalid because the same key occurs more than once:
@DupKey[k1=4,k1=5] // illegal duplicate key
table t {
/* body omitted */
}
Illegal Duplicate Structured Annotation
The following example is invalid because the annotation name occurs more than
once on the same element, e.g. table t:
@DupAnno[k1=4]
@DupAnno[k2=5] // illegal duplicate name
table t {
/* body omitted */
}
Illegal Simultaneous Use of Both Structured and Unstructured Annotation
The following example is invalid because the annotation name is used by both
an unstructured and structured annotation on the same element table t:
@MixAnno("Anything")
@MixAnno[k2=5] // illegal use in both annotation types
table t {
/* body omitted */
}
20.2. Predefined annotations
Annotation names that start with lowercase letters are reserved for the
standard library and architecture. This document pre-defines a set of
"standard" annotations in Appendix C. We expect that this
list will grow. We encourage custom architectures to define annotations
starting with a manufacturer prefix: e.g., an organization named X would use
annotations named like @X_annotation
20.2.1. Optional parameter annotations
A parameter to a package, parser type, control type, extern method, extern
function or extern object constructor can be annotated with @optional to
indicate that the user does not need to provide a corresponding argument for
that parameter. The meaning of a parameter with no supplied value is
target-dependent.
20.2.2. Annotations on the table action list
The following two annotations can be used to give additional information to the compiler and control-plane about actions in a table. These annotations have no bodies.
-
@tableonly: actions with this annotation can only appear within the table, and never as default action. -
@defaultonly: actions with this annotation can only appear in the default action, and never in the table.
table t {
actions = {
a, // can appear anywhere
@tableonly b, // can only appear in the table
@defaultonly c, // can only appear in the default action
}
/* body omitted */
}
20.2.3. Control-plane API annotations
The @name annotation directs the compiler to use a different local name when
generating the external APIs used to manipulate a language element from the
control plane. This annotation takes a local compile-time known value of type
string (typically a string literal). In the following example, the
fully-qualified name of the table is c_inst.t1.
control c( /* parameters omitted */ )() {
@name("t1") table t { /* body omitted */ }
apply { /* body omitted */ }
}
c() c_inst;
The @hidden annotation hides a controllable entity, e.g. a table, key,
action, or extern, from the control plane. This effectively removes its
fully-qualified name (Section 10.1.1). This annotation does
not have a body.
20.2.3.1. Restrictions
Each element may be annotated with at most one @name or @hidden annotation,
and each control plane name must refer to at most one controllable entity. This
is of special concern when using an absolute @name annotation: if a type
containing a @name annotation with an absolute pathname (i.e., one starting
with a dot) is instantiated more than once, it will result in the same name
referring to two controllable entities.
control noargs();
package top(noargs c1, noargs c2);
control c() {
@name(".foo.bar") table t { /* body omitted */ }
apply { /* body omitted */ }
}
top(c(), c()) main;
Without the @name annotation, this program would produce two controllable
entities with fully-qualified names main.c1.t and main.c2.t. However, the
@name(".foo.bar") annotation renames table t in both instances to
foo.bar, resulting in one name that refers to two controllable entities,
which is illegal.
20.2.4. Concurrency control annotations
The @atomic annotation, described in Section 7.4.5 can be used to
enforce the atomic execution of a code block.
20.2.5. Branch prediction annotations
The @likely and @unlikely annotation can be used on blocks that are used in
if-else or switch statements to hint when a branch is likely or unlikely
to be taken.
if (error_failure) @unlikely {
/* code that is unlikely to execute as the condition is probably false */
}
switch(value) {
1: @likely {
/* code for the most common case */
}
2: { /* ... */ }
default: @unlikely {
/* code for rare case */
}
}
These annotations have no effect on the semantics of the program; they are just
hints to help an optimizer. A warning may be issued if a @likely block is
found to never execute or an @unlikely block will always execute, but is not
required.
20.2.6. Value set annotations
The @match annotation, described in Section 15.6, is used to
specify a match_kind value other than the default match_kind of exact for
a field of a value_set.
20.2.7. Extern function/method annotations
Various annotations may appear on extern function and method declarations to describe limitations on the behavior and interactions of those functions. By default extern functions might have any effect on the environment of the P4 program and might interact in non-trivial ways (subject to a few limitations — see [sec-calling-convention]). Since externs are architecture-specific and their behavior is known to the architecture definition, these annotations are not strictly necessary (an implementation can have knowledge of how externs interact based on their names built into it), but these annotations provide a uniform way of describing certain well-defined interactions (or their absence), allowing architecture-independent analysis of P4 programs.
-
@pure- Describes a function that depends solely on itsinparameter values, and has no effect other than returning a value, and copy-out behavior on itsoutandinoutparameters. No hidden state is recorded between calls, and its value does not depend on any hidden state that may be changed by other calls. An example is ahashfunction that computes a deterministic hash of its arguments, and its return value does not depend upon any control-plane writable seed or initialization vector value. A@purefunction whose results are unused may be safely eliminated with no adverse effects, and multiple calls with identical arguments may be combined into a single call (subject to the limits imposed by copy-out behavior ofoutandinoutparameters).@purefunctions may also be reordered with respect to other computations that are not data dependent. -
@noSideEffects- Weaker than@pureand describes a function that does not change any hidden state, but may depend on hidden state. One example is ahashfunction that computes a deterministic hash of its arguments, plus some internal state that can be modified via control plane API calls such as a seed or initialization vector. Another example is a read of one element of a register array extern object. Such a function may be dead code eliminated, and may be reordered or combined with other@noSideEffectsor@purecalls (subject to the limits imposed by copy-out behavior ofoutandinoutparameters), but not with other function calls that may have side effects that affect the function.
20.2.8. Deprecated annotation
The deprecated annotation has a required string argument that is a message
that will be printed by a compiler when a program is using the deprecated
construct. This is mostly useful for annotating library constructs, such as
externs. The parameter must be a local compile-time known value of type
string.
#define DEPR_V1_2_2 "Deprecated in v1.2.2"
@deprecated("Please use the 'check' function instead." ++ DEPR_V1_2_2)
extern Checker {
/* body omitted */
}
20.2.9. No warnings annotation
The noWarn annotation has a required string argument that indicates a
compiler warning that will be inhibited. For example @noWarn("unused") on a
declaration will prevent a compiler warning if that declaration is not used.
The parameter must be a local compile-time known value of type string.
=== Target-specific annotations
Each P4 compiler implementation can define additional annotations specific to the target of the compiler. The syntax of the annotations should conform to the above description. The semantics of such annotations is target-specific. They could be used in a similar way to pragmas in other languages.
The P4 compiler should provide:
-
Errors when annotations are used incorrectly (e.g., an annotation expecting a parameter but used without arguments, or with arguments of the wrong type)
-
Warnings for unknown annotations.
Appendix A: Revision History
A.1. Summary of changes made in unreleased version
-
Clarified that numeric priorities cannot be assigned to entries of a table that has
const entries([sec-entries]). -
Clarified that
switchstatements are allowed in action and function bodies, and thatswitchstatements withaction_runexpressions are only allowed in controlapplyblocks ([sec-stmts] and [sec-switch-stmt]). -
Added standard branch prediction annotations (Section 20.2.5)
-
Added support for compile-time string concatenation using
++operator ([sec-string-type] and [sec-string-ops]). -
Added compound assignment statements ([sec-assignment])
-
Update the specification grammar for
forloops to match the grammar used in thep4cimplementation ([sec-loop-stmt]).
A.2. Summary of changes made in version 1.2.5, released October 11, 2024
-
Improved type nesting rules ([sec-type-nesting]).
-
Clarified that directionless extern parameters are passed by reference.
-
Introduced distinction between local compile-time known and compile-time known values (Section 7.5).
A.3. Summary of changes made in version 1.2.4,released May 15, 2023
-
Added header stack expressions ([sec-hs-init]).
-
Allow casts from a type to itself ([sec-casts]).
-
Added an invalid header or header union expression
{#}([sec-ops-on-hdrs] and [sec-expr-hu]). -
Added a concept of numeric values ([sec-numeric-values]).
-
Added a section on operations on extern objects ([sec-extern-operations]).
-
Added note in sections operations on types for types that support compile-time size determination.
-
Clarified that header stacks are arrays of headers or header unions.
-
Added distinctness of fields for types that have fields including error, match kind, struct, header, and header union.
-
Clarified types
bit<W>,int<W>, andvarbit<W>encompass the case where the width is a compile-time known expression evaluating to an appropriate integer ([sec-unsigned-integers], [sec-signed-integers], [sec-dynamically-sized-integers]). -
Clarified restrictions for parameters with default values ([sec-calling-convention]).
-
Added optional trailing commas ([sec-trailing-commas]).
-
Clarified the scope of parser namespaces ([sec-parser-decl]).
-
Specified that algorithm for generating control-plane names for keys is optional ([sec-cp-keys]).
-
Clarified types of expressions that may appear in
select([sec-select]). -
Added description of semantics of the core.p4 match kinds ([sec-table-keys]).
-
Explicitly disallow overloading of parsers, controls, and packages ([sec-extern-objects]).
-
Clarified implicit casts present in select expressions ([sec-select]).
-
Clarified that slices can be applied to arbitrary-precision integers ([sec-varint-ops]).
-
Clarified that direct invocation is not possible for objects that have constructor arguments ([sec-direct-invocation]).
-
Added comparison for tuples as a legal operation ([sec-tuple-exprs]).
-
Clarified the behavior of
lookaheadon header-typed values (Section 15.8.3). -
Added
static_assertfunction ([sec-static-assert]). -
Clarified semantics of ranges where the start is bigger than the end ([sec-ranges]).
-
Allow ranges to be specified by serializable enums ([sec-ranges]).
-
Specified type produced by the
*sizeInB*methods ([sec-minsizeinbits]). -
Added section with operations on
match_kindvalues ([sec-match-kind-exprs]). -
Renamed infinite-precision integers to arbitrary-precision integers ([sec-arbitrary-precision-integers]).
-
compiler-inserted
default_actionis notconst([sec-tables]). -
Clarified the restrictions on run time for tables with
const entries([sec-entries]). -
renamed list expressions to tuple expressions
-
Added
listtype ([sec-list-types]). -
Defined
entriestable property withoutconst, for entries installed when the P4 program is loaded, but the control plane can later change them or add to them ([sec-entries]). -
Clarified behavior of table with no
keyproperty, or if its list of keys is empty ([sec-table-keys]).
A.4. Summary of changes made in version 1.2.3, released July 11, 2022.
-
Extended
minSizeInBitsandminSizeInBytesto apply to more expressions ([sec-minsizeinbits]). -
Added support for
maxSizeInBitsandmaxSizeInBytes([sec-minsizeinbits]). -
Added support for empty lists of const entries in tables ([sec-entries]).
-
Added support for
switchstatements in actions ([sec-actions]). -
Added support for direct invocation of controls and parsers ([sec-parameterization]).
-
Added parser
value_setto list of control-plane visible names ([sec-cp-names]). -
Added
match_kindas a base type ([sec-match-kind-type]). -
Removed structure initializers as they are subsumed by structure-valued expressions ([sec-structure-expressions]).
-
Specified operations on values typed as type variables ([sec-type-variable-operations]).
-
Clarified semantics of compile-time known values (Section 7.5).
-
Clarified semantics of directionless parameters ([sec-calling-convention]).
-
Clarified semantics of arbitrary precision integers ([sec-arbitrary-precision-integers]).
-
Clarified semantics of bit slices, shifts, and concatenation ([sec-bit-ops]).
-
Clarified semantics of optional parameters ([sec-optional-parameters]).
-
Clarified restrictions on extern method and function invocation ([sec-calling-restrictions]).
-
Clarified semantics of implicit casts ([sec-implicit-casts]).
A.5. Summary of changes made in version 1.2.2, released May 17, 2021
-
Added support for accessing tuple fields ([sec-tuple-exprs]).
-
Added support for generic structures ([sec-type-spec]).
-
Added support for integers,
enums, anderrors inswitchstatements ([sec-switch-stmt]). -
Added support for additional enumeration types ([sec-enum-types]).
-
Added support for abstract methods ([sec-abstract-methods]).
-
Added support for conditional statements and empty statements in parsers ([sec-parser-state-stmt]).
-
Added support for casts from
inttobool([sec-casts]). -
Added support for 0-width bitstrings and varbits ([sec-uninitialized-values-and-writing-invalid-headers]).
-
Clarified that
default_actionisNoActionif otherwise unspecified ([sec-tables]). -
Clarified the types of expressions that may be used as indexes for header stacks ([sec-expr-hs]).
-
Clarified representation of Booleans in headers ([sec-header-types]).
-
Clarified representation of empty types ([sec-uninitialized-values-and-writing-invalid-headers]).
-
Clarified that action data can be specified by the control plane,
default_actiontable property, orconst entriestable property ([sec-actions]). -
Fixed several typos and inconsistencies in grammar (Appendix E).
-
Eliminated annotations on
constentries in grammar (Appendix E).
A.6. Summary of changes made in version 1.2.1, released June 11, 2020
-
Added structure-value expressions ([sec-structure-expressions]).
-
Added support for default values ([sec-default-values]).
-
Added support for concatenating signed strings ([sec-concatenation]).
-
Added key-value and list-structured annotations ([sec-annotations]).
-
Added
@pureand@noSideEffectsannotations (Section 20.2.7). -
Added
@noWarnannotation (Section 20.2.9). -
Generalized typing for masks to allow serializable
enums ([sec-cubes]). -
Restricted the right operands of bit shifts involving arbitrary-precision integers to be constant and positive ([sec-varint-ops]).
-
Clarified copy-out behavior for
return([sec-return-stmt]) andexit([sec-exit-stmt]) statements. -
Clarified semantics of invalid header stacks ([sec-uninitialized-values-and-writing-invalid-headers]).
-
Clarified initialization semantics ([sec-lvalues] and [sec-calling-convention]), especially for headers and local variables.
-
Clarified evaluation order for table keys ([sec-mau-semantics]).
-
Fixed grammar to clarify parsing of right shift operator (
>>), allow empty statements in parser ([sec-parser-state-stmt]), and eliminate annotations on const entries ([sec-entries]).
A.7. Summary of changes made in version 1.2.0, released October 14, 2019
-
Added
table.apply().miss([sec-invoke-mau]). -
Added
stringtype ([sec-string-type]). -
Added implicit casts from enum values ([sec-enum-exprs]).
-
Allow 1-bit signed values
-
Define the type of bit slices from signed and unsigned values to be unsigned.
-
Constrain
defaultlabel position forswitchstatements. -
Allow empty tuples.
-
Added
@deprecatedannotation. -
Relaxed the structure of annotation bodies.
-
Removed the
@pkginfoannotation, which is now defined by the P4Runtime specification. -
Added
inttype ([sec-arbitrary-precision-integers]). -
Added error
ParserInvalidArgument([sec-packet-extract-two], Section 15.8.4). -
Clarified the significance of order of entries in
const entries([sec-entries]). -
Added methods to calculate header size ([sec-ops-on-hdrs]).
A.8. Summary of changes made in version 1.1.0, released November 26, 2017.
-
Top-level functions ([sec-functions])
-
Functions may be declared at the top-level of a P4 program.
-
-
Optional and named parameters ([sec-calling-convention])
-
Parameters may be specified by name, with a default value, or designated as optional.
-
-
enumrepresentations ([sec-enum-exprs])-
enumvalues to be specified with a concrete representation.
-
-
Parser values sets ([sec-value-set])
-
value_setobjects for control-plane programmableselectlabels.
-
-
Type definitions (Section 8.5.8)
-
New types may be introduced in programs.
-
-
Saturating arithmetic ([sec-bit-ops])
-
Saturating arithmetic is supported on some targets.
-
-
Structured annotations ([sec-annotations])
-
Annotations may be specified as lists of key-value pairs
-
-
Globalname ([sec-name-annotations])
-
The reserved
globalnameannotation has been removed.
-
-
Table
sizeproperty ([sec-size-table-property])-
Meaning of optional
sizeproperty for tables has been defined.
-
-
Invalid headers ([sec-ops-on-hdrs])
-
Clarified semantics of operations on invalid headers.
-
-
Calling restrictions ([sec-calling-restrictions])
-
Added restrictions on kinds of values that may be passed as arguments to calls.
-
-
Bitwise operator precedence (Appendix E)
-
Modified precedence conventions so that bitwise operators
&|and^have higher precedence than relation operators<><=>=.
-
-
Computed bitwidths ([sec-base-types])
-
Added support for specifying widths using expressions in
bitandvarbittypes.
-
A.9. Initial version 1.0.0, released May 17, 2017
Appendix B: P4 reserved keywords
The following table shows all P4 reserved keywords. Some identifiers
are treated as keywords only in specific contexts (e.g., the keyword actions).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Appendix C: P4 reserved annotations
The following table shows all P4 reserved annotations.
| Annotation | Purpose | See Section |
|---|---|---|
|
specify atomic execution |
|
|
action can only appear in the default action |
|
|
hides a controllable entity from the control plane |
|
|
specify |
|
|
assign local control-plane name |
|
|
parameter is optional |
|
|
action cannot be a default_action |
|
|
Construct has been deprecated |
|
|
pure function |
|
|
function with no side effects |
|
|
Has a string argument; inhibits compiler warnings |
Appendix D: P4 core library
The P4 core library contains declarations that are useful to most programs.
For example, the core library includes the declarations of the
predefined packet_in and packet_out extern objects, used
in parsers and deparsers to access packet data.
/// Standard error codes. New error codes can be declared by users.
error {
NoError, /// No error.
PacketTooShort, /// Not enough bits in packet for 'extract'.
NoMatch, /// 'select' expression has no matches.
StackOutOfBounds, /// Reference to invalid element of a header stack.
HeaderTooShort, /// Extracting too many bits into a varbit field.
ParserTimeout, /// Parser execution time limit exceeded.
ParserInvalidArgument /// Parser operation was called with a value
/// not supported by the implementation.
}
extern packet_in {
/// Read a header from the packet into a fixed-sized header @hdr
/// and advance the cursor.
/// May trigger error PacketTooShort or StackOutOfBounds.
/// @T must be a fixed-size header type
void extract<T>(out T hdr);
/// Read bits from the packet into a variable-sized header @variableSizeHeader
/// and advance the cursor.
/// @T must be a header containing exactly 1 varbit field.
/// May trigger errors PacketTooShort, StackOutOfBounds, or HeaderTooShort.
void extract<T>(out T variableSizeHeader,
in bit<32> variableFieldSizeInBits);
/// Read bits from the packet without advancing the cursor.
/// @returns: the bits read from the packet.
/// T may be an arbitrary fixed-size type.
T lookahead<T>();
/// Advance the packet cursor by the specified number of bits.
void advance(in bit<32> sizeInBits);
/// @return packet length in bytes. This method may be unavailable on
/// some target architectures.
bit<32> length();
}
extern packet_out {
/// Write @data into the output packet, skipping invalid headers
/// and advancing the cursor
/// @T can be a header type, a header stack, a header_union, or a struct
/// containing fields with such types.
void emit<T>(in T data);
}
action NoAction() {}
/// Standard match kinds for table key fields.
/// Some architectures may not support all these match kinds.
/// Architectures can declare additional match kinds.
match_kind {
/// Match bits exactly.
exact,
/// Ternary match, using a mask.
ternary,
/// Longest-prefix match.
lpm
}
/// Static assert evaluates a boolean expression
/// at compilation time. If the expression evaluates to
/// false, compilation is stopped and the corresponding message is printed.
/// The function returns a boolean, so that it can be used
/// as a global constant value in a program, e.g.:
/// const version = static_assert(V1MODEL_VERSION > 20180000, "Expected a v1 model version >= 20180000");
extern bool static_assert(bool check, string message);
/// Like the above but using a default message.
extern bool static_assert(bool check);
Appendix E: P4 grammar
This is the grammar of P416 written using the YACC/bison language. Absent from this grammar is the precedence of various operations.
The grammar is actually ambiguous, so the lexer and the parser must collaborate for parsing the language. In particular, the lexer must be able to distinguish two kinds of identifiers:
-
Type names previously introduced (
TYPE_IDENTIFIERtokens) -
Regular identifiers (
IDENTIFIERtoken)
The parser has to use a symbol table to indicate to the lexer how to parse subsequent appearances of identifiers. For example, given the following program fragment:
typedef bit<4> t;
struct s { /* body omitted */}
t x;
parser p(bit<8> b) { /* body omitted */ }
The lexer has to return the following terminal kinds:
t - TYPE_IDENTIFIER
s - TYPE_IDENTIFIER
x - IDENTIFIER
p - TYPE_IDENTIFIER
b - IDENTIFIER
This grammar has been heavily influenced by limitations of the Bison parser generator tool.
The STRING_LITERAL token corresponds to a string literal
enclosed within double quotes, as described in Section 6.2.2.3.
All other terminals are uppercase spellings of the corresponding
keywords. For example, RETURN is the terminal returned by the
lexer when parsing the keyword return.
trailingCommaOpt
: /* empty */
| ,
;
booleanLiteral
: TRUE
| FALSE
;
integerLiteral
: D int
| nat W int
| nat S int
;
stringLiteral
: " text "
;
identifier
: `ID text
;
typeIdentifier
: `TID text
;
nonTypeName
: identifier
| APPLY
| KEY
| ACTIONS
| STATE
| ENTRIES
| TYPE
| PRIORITY
;
prefixedNonTypeName
: nonTypeName
| `ID . nonTypeName
;
typeName = typeIdentifier
prefixedTypeName
: typeName
| `TID . typeName
;
tableCustomName
: identifier
| typeIdentifier
| APPLY
| STATE
| TYPE
| PRIORITY
;
name
: nonTypeName
| typeName
| LIST
;
nameList
: name
| nameList , name
;
member = name
direction
: /* empty */
| IN
| OUT
| INOUT
;
baseType
: BOOL
| ERROR
| MATCH_KIND
| STRING
| INT
| INT `< int >
| INT `< `( expression ) >
| BIT
| BIT `< int >
| BIT `< `( expression ) >
| VARBIT `< int >
| VARBIT `< `( expression ) >
;
specializedType
: prefixedTypeName `< typeArgumentList >
;
namedType
: prefixedTypeName
| specializedType
;
headerStackType
: namedType `[ expression ]
;
listType
: LIST `< typeArgument >
;
tupleType
: TUPLE `< typeArgumentList >
;
type
: baseType
| namedType
| headerStackType
| listType
| tupleType
;
typeOrVoid
: type
| VOID
| identifier
;
typeParameter = name
typeParameterList
: typeParameter
| typeParameterList , typeParameter
;
typeParameterListOpt
: /* empty */
| `< typeParameterList >
;
parameter
: annotationList direction type name initializerOpt
;
nonEmptyParameterList
: parameter
| nonEmptyParameterList , parameter
;
parameterList
: /* empty */
| nonEmptyParameterList
;
constructorParameter = parameter
constructorParameterList = parameterList
constructorParameterListOpt
: /* empty */
| `( parameterList )
;
namedExpression
: name = expression
;
namedExpressionList
: namedExpression
| namedExpressionList , namedExpression
;
literalExpression
: booleanLiteral
| integerLiteral
| stringLiteral
;
referenceExpression
: prefixedNonTypeName
| THIS
;
defaultExpression
: ...
;
unop
: !
| ~
| -
| +
;
unaryExpression
: unop expression
;
binop
: *
| /
| %
| +
| -
| |+|
| |-|
| <<
| >>
| <=
| >=
| <
| >
| !=
| ==
| &
| ^
| |
| ++
| &&
| ||
;
binaryExpression
: expression binop expression
;
binaryExpressionNonBrace
: expressionNonBrace binop expression
;
ternaryExpression
: expression ? expression : expression
;
ternaryExpressionNonBrace
: expressionNonBrace ? expression : expression
;
castExpression
: `( type ) expression
;
dataExpression
: invalidHeaderExpression
| sequenceOrRecordExpression
;
errorAccessExpression
: ERROR . member
;
memberAccessExpression
: memberAccessBase . member
;
indexAccessExpression
: expression `[ expression ]
;
sliceAccessExpression
: expression `[ expression : expression ]
;
accessExpression
: errorAccessExpression
| memberAccessExpression
| indexAccessExpression
| sliceAccessExpression
;
memberAccessExpressionNonBrace
: memberAccessBaseNonBrace . member
;
indexAccessExpressionNonBrace
: expressionNonBrace `[ expression ]
;
sliceAccessExpressionNonBrace
: expressionNonBrace `[ expression : expression ]
;
accessExpressionNonBrace
: errorAccessExpression
| memberAccessExpressionNonBrace
| indexAccessExpressionNonBrace
| sliceAccessExpressionNonBrace
;
callExpression
: callTarget `( argumentList )
| callableTarget `< realTypeArgumentList > `( argumentList )
;
callExpressionNonBrace
: callTargetNonBrace `( argumentList )
| callableTargetNonBrace `< realTypeArgumentList > `( argumentList )
;
parenthesizedExpression
: `( expression )
;
expression
: literalExpression
| referenceExpression
| defaultExpression
| unaryExpression
| binaryExpression
| ternaryExpression
| castExpression
| dataExpression
| accessExpression
| callExpression
| parenthesizedExpression
;
expressionList
: /* empty */
| expression
| expressionList , expression
;
memberAccessBase
: prefixedTypeName
| expression
;
sequenceElementExpression = expressionList
recordElementExpression
: name = expression
| name = expression , ...
| name = expression , namedExpressionList
| name = expression , namedExpressionList , ...
;
sequenceOrRecordElementExpression
: sequenceElementExpression
| recordElementExpression
;
callableTarget = expression
constructorTarget = namedType
callTarget
: callableTarget
| constructorTarget
;
expressionNonBrace
: literalExpression
| referenceExpression
| unaryExpression
| binaryExpressionNonBrace
| ternaryExpressionNonBrace
| castExpression
| accessExpressionNonBrace
| callExpressionNonBrace
| parenthesizedExpression
;
memberAccessBaseNonBrace
: prefixedTypeName
| expressionNonBrace
;
callableTargetNonBrace = expressionNonBrace
callTargetNonBrace
: callableTargetNonBrace
| constructorTarget
;
simpleKeysetExpression
: expression
| expression &&& expression
| expression .. expression
| DEFAULT
| _
;
simpleKeysetExpressionList
: simpleKeysetExpression
| simpleKeysetExpressionList , simpleKeysetExpression
;
tupleKeysetExpression
: `( expression &&& expression )
| `( expression .. expression )
| `( DEFAULT )
| `( _ )
| `( simpleKeysetExpression , simpleKeysetExpressionList )
;
keysetExpression
: simpleKeysetExpression
| tupleKeysetExpression
;
realTypeArgument
: type
| VOID
| _
;
realTypeArgumentList
: realTypeArgument
| realTypeArgumentList , realTypeArgument
;
typeArgument
: realTypeArgument
| nonTypeName
;
typeArgumentList
: /* empty */
| typeArgument
| typeArgumentList , typeArgument
;
argument
: expression
| name = expression
| name = _
| _
;
argumentListNonEmpty
: argument
| argumentListNonEmpty , argument
;
argumentList
: /* empty */
| argumentListNonEmpty
;
lvalue
: referenceExpression
| lvalue . member
| lvalue `[ expression ]
| lvalue `[ expression : expression ]
| `( lvalue )
;
emptyStatement
: ;
;
assignmentStatement
: lvalue assignop expression ;
;
callStatement
: lvalue `( argumentList ) ;
| lvalue `< typeArgumentList > `( argumentList ) ;
;
directApplicationStatement
: namedType . APPLY `( argumentList ) ;
;
returnStatement
: RETURN ;
| RETURN expression ;
;
exitStatement
: EXIT ;
;
blockStatement
: annotationList `{ blockElementStatementList }
;
conditionalStatement
: IF `( expression ) statement
| IF `( expression ) statement ELSE statement
;
forUpdateStatement
: lvalue `( argumentList )
| lvalue `< typeArgumentList > `( argumentList )
| lvalue assignop expression
;
forUpdateStatementListNonEmpty
: forUpdateStatement
| forUpdateStatementListNonEmpty , forUpdateStatement
;
forUpdateStatementList
: /* empty */
| forUpdateStatementListNonEmpty
;
forInitStatement
: annotationList type name initializerOpt
| forUpdateStatement
;
forInitStatementListNonEmpty
: forInitStatement
| forInitStatementListNonEmpty , forInitStatement
;
forInitStatementList
: /* empty */
| forInitStatementListNonEmpty
;
forCollectionExpression
: expression
| expression .. expression
;
forStatement
: annotationList FOR
`( forInitStatementList ; expression ; forUpdateStatementList ) statement
| annotationList FOR `( type name IN forCollectionExpression ) statement
| annotationList FOR
`( annotationListNonEmpty type name IN forCollectionExpression )
statement
;
switchLabel
: DEFAULT
| expressionNonBrace
;
switchCase
: switchLabel : blockStatement
| switchLabel :
;
switchCaseList
: /* empty */
| switchCaseList switchCase
;
switchStatement
: SWITCH `( expression ) `{ switchCaseList }
;
breakStatement
: BREAK ;
;
continueStatement
: CONTINUE ;
;
statement
: emptyStatement
| assignmentStatement
| callStatement
| directApplicationStatement
| returnStatement
| exitStatement
| blockStatement
| conditionalStatement
| forStatement
| breakStatement
| continueStatement
| switchStatement
;
initializer
: = expression
;
constantDeclaration
: annotationList CONST type name initializer ;
;
initializerOpt
: /* empty */
| initializer
;
variableDeclaration
: annotationList type name initializerOpt ;
;
blockElementStatement
: constantDeclaration
| variableDeclaration
| statement
;
blockElementStatementList
: /* empty */
| blockElementStatementList blockElementStatement
;
functionPrototype
: typeOrVoid name typeParameterListOpt `( parameterList )
;
functionDeclaration
: annotationList functionPrototype blockStatement
;
actionDeclaration
: annotationList ACTION name `( parameterList ) blockStatement
;
objectInitializer
: = `{ objectDeclarationList }
;
instantiation
: annotationList type `( argumentList ) name ;
| annotationList type `( argumentList ) name objectInitializer ;
;
objectDeclaration
: functionDeclaration
| instantiation
;
objectDeclarationList
: /* empty */
| objectDeclarationList objectDeclaration
;
errorDeclaration
: ERROR `{ nameList }
;
matchKindDeclaration
: MATCH_KIND `{ nameList trailingCommaOpt }
;
enumTypeDeclaration
: annotationList ENUM name `{ nameList trailingCommaOpt }
| annotationList ENUM type name `{ namedExpressionList trailingCommaOpt }
;
typeField
: annotationList type name ;
;
typeFieldList
: /* empty */
| typeFieldList typeField
;
structTypeDeclaration
: annotationList STRUCT name typeParameterListOpt `{ typeFieldList }
;
headerTypeDeclaration
: annotationList HEADER name typeParameterListOpt `{ typeFieldList }
;
headerUnionTypeDeclaration
: annotationList HEADER_UNION name typeParameterListOpt `{ typeFieldList }
;
derivedTypeDeclaration
: enumTypeDeclaration
| structTypeDeclaration
| headerTypeDeclaration
| headerUnionTypeDeclaration
;
typedef
: type
| derivedTypeDeclaration
;
typedefDeclaration
: annotationList TYPEDEF typedef name ;
| annotationList TYPE type name ;
;
externFunctionDeclaration
: annotationList EXTERN functionPrototype ;
;
externConstructorPrototype
: annotationList typeIdentifier `( parameterList ) ;
;
externMethodPrototype
: annotationList functionPrototype ;
| annotationList ABSTRACT functionPrototype ;
;
externConstructorOrMethodPrototype
: externConstructorPrototype
| externMethodPrototype
;
externConstructorOrMethodPrototypeList
: /* empty */
| externConstructorOrMethodPrototypeList externConstructorOrMethodPrototype
;
externObjectDeclaration
: annotationList EXTERN nonTypeName typeParameterListOpt
`{ externConstructorOrMethodPrototypeList }
;
externDeclaration
: externFunctionDeclaration
| externObjectDeclaration
;
selectCase
: keysetExpression : name ;
;
selectCaseList
: /* empty */
| selectCaseList selectCase
;
selectExpression
: SELECT `( expressionList ) `{ selectCaseList }
;
stateExpression
: name ;
| selectExpression
;
transitionStatement
: /* empty */
| TRANSITION stateExpression
;
valueSetType
: baseType
| tupleType
| prefixedTypeName
;
valueSetDeclaration
: annotationList VALUE_SET `< valueSetType > `( expression ) name ;
;
parserTypeDeclaration
: annotationList PARSER name typeParameterListOpt `( parameterList ) ;
;
parserBlockStatement
: annotationList `{ parserStatementList }
;
parserConditionalStatement
: IF `( expression ) parserStatement
| IF `( expression ) parserStatement ELSE parserStatement
;
parserStatement
: constantDeclaration
| variableDeclaration
| emptyStatement
| assignmentStatement
| callStatement
| directApplicationStatement
| parserBlockStatement
| parserConditionalStatement
;
parserStatementList
: /* empty */
| parserStatementList parserStatement
;
parserState
: annotationList STATE name `{ parserStatementList transitionStatement }
;
parserStateList
: parserState
| parserStateList parserState
;
parserLocalDeclaration
: constantDeclaration
| instantiation
| variableDeclaration
| valueSetDeclaration
;
parserLocalDeclarationList
: /* empty */
| parserLocalDeclarationList parserLocalDeclaration
;
parserDeclaration
: annotationList PARSER name typeParameterListOpt `( parameterList )
constructorParameterListOpt
`{ parserLocalDeclarationList parserStateList }
;
const
: CONST
;
constOpt
: /* empty */
| const
;
tableKey
: expression : name annotationList ;
;
tableKeyList
: /* empty */
| tableKeyList tableKey
;
tableActionReference
: prefixedNonTypeName
| prefixedNonTypeName `( argumentList )
;
tableAction
: annotationList tableActionReference ;
;
tableActionList
: /* empty */
| tableActionList tableAction
;
tableEntryPriority
: PRIORITY = integerLiteral :
| PRIORITY = `( expression ) :
;
tableEntry
: constOpt tableEntryPriority keysetExpression : tableActionReference
annotationList ;
| constOpt keysetExpression : tableActionReference annotationList ;
;
tableEntryList
: /* empty */
| tableEntryList tableEntry
;
tableProperty
: KEY = `{ tableKeyList }
| ACTIONS = `{ tableActionList }
| annotationList constOpt ENTRIES = `{ tableEntryList }
| annotationList constOpt tableCustomName initializer ;
;
tablePropertyList
: /* empty */
| tablePropertyList tableProperty
;
tableDeclaration
: annotationList TABLE name `{ tablePropertyList }
;
controlTypeDeclaration
: annotationList CONTROL name typeParameterListOpt `( parameterList ) ;
;
controlBody = blockStatement
controlLocalDeclaration
: constantDeclaration
| instantiation
| variableDeclaration
| actionDeclaration
| tableDeclaration
;
controlLocalDeclarationList
: /* empty */
| controlLocalDeclarationList controlLocalDeclaration
;
controlDeclaration
: annotationList CONTROL name typeParameterListOpt `( parameterList )
constructorParameterListOpt
`{ controlLocalDeclarationList APPLY controlBody }
;
packageTypeDeclaration
: annotationList PACKAGE name typeParameterListOpt `( parameterList ) ;
;
typeDeclaration
: derivedTypeDeclaration
| typedefDeclaration
| parserTypeDeclaration
| controlTypeDeclaration
| packageTypeDeclaration
;
declaration
: constantDeclaration
| instantiation
| functionDeclaration
| actionDeclaration
| errorDeclaration
| matchKindDeclaration
| externDeclaration
| parserDeclaration
| controlDeclaration
| typeDeclaration
;
annotationToken
: UNEXPECTED_TOKEN
| ABSTRACT
| ACTION
| ACTIONS
| APPLY
| BOOL
| BIT
| BREAK
| CONST
| CONTINUE
| CONTROL
| DEFAULT
| ELSE
| ENTRIES
| ENUM
| ERROR
| EXIT
| EXTERN
| FALSE
| FOR
| HEADER
| HEADER_UNION
| IF
| IN
| INOUT
| INT
| KEY
| MATCH_KIND
| TYPE
| OUT
| PARSER
| PACKAGE
| PRAGMA
| RETURN
| SELECT
| STATE
| STRING
| STRUCT
| SWITCH
| TABLE
| THIS
| TRANSITION
| TRUE
| TUPLE
| TYPEDEF
| VARBIT
| VALUE_SET
| LIST
| VOID
| _
| identifier
| typeIdentifier
| stringLiteral
| integerLiteral
| &&&
| ..
| <<
| &&
| ||
| ==
| !=
| >=
| <=
| ++
| +
| |+|
| -
| |-|
| *
| /
| %
| |
| &
| ^
| ~
| [
| ]
| {
| }
| <
| >
| !
| :
| ,
| ?
| .
| =
| ;
| @
;
annotationBody
: /* empty */
| annotationBody `( annotationBody )
| annotationBody annotationToken
;
structuredAnnotationBody
: sequenceOrRecordElementExpression trailingCommaOpt
;
annotation
: @ name
| @ name `( annotationBody )
| @ name `[ structuredAnnotationBody ]
| @ PRAGMA name annotationBody
;
annotationListNonEmpty
: annotation
| annotationListNonEmpty annotation
;
annotationList
: /* empty */
| annotationListNonEmpty
;
p4program
: /* empty */
| p4program declaration
| p4program ;
;