Registers size for flow tracking

Hi @sarahshakeri ,

  1. You can find the definition in the language spec, let me show:

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);
}

Therefore, T is the size of the information you want to store. Let’s say you want to store numbers 0 to 3 (imagine this is a value of a header field). Then bit<2> for T should be enough. But you want to store that number for every packet coming in every of your hypothetical 16 ports. Then you can instantiate is as register<T>(16). But, because mas size for registers in V1Model is 232, you could in principle make it as big as 232. This is further explained n the specification:

The type T has to be specified when instantiating a set of registers, by specializing the Register type:

Register<bit<16>>(128) registerBank; //I modified T size to 16

The instantiation of registerBank is made using the Register type specialized with the bit<16> bound to the T type argument.

If this was not clear enough, maybe the following figure makes it clear:

  1. Oh, I see. I understand now. I meant that, if you implement an example that counts every time you write in a register index, then it counts every time you write but not every time you write in a register for the first time (because you would have to make it possible to monitor that, which is complicated I guess.). But if you increment the index by yourself and then count, it will work as you expect. :slight_smile:

  2. To be honest, I lack a deep knowledge about how this is handled in hardware or software switches. But let’s say two packets are received at the same time if you write to a register, I guess the switch has to handle that somewhat sequentially or blocking the access for writing if you write actions have to happen pretty much at the same time. However, P4 allows the operations to be performed in a @atomic way. In particular, if you need to save the index of the last written register, then you need to write and read atomically, and then let the next packet’s write and read bet atomically done too (and so on). Let me show what the specification says about it:

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.

In other words, you need to atomically write and read to prevent another write operation to change the value in the nanoseconds between the write and read operations of your action.

I am sorry for the long response. I hope I made no mistake but if anyone points and error please write a reply and I will fix the answer :slight_smile:

Cheers,

2 Likes