Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

State and repeated invocations #926

Closed
hackedy opened this issue Mar 17, 2021 · 22 comments
Closed

State and repeated invocations #926

hackedy opened this issue Mar 17, 2021 · 22 comments
Labels

Comments

@hackedy
Copy link
Contributor

hackedy commented Mar 17, 2021

Hi, I apologize if this has already been addressed in issues here. I'm trying to determine what's supposed to happen if a control instance apply() is called twice. Is the state of local variables persisted between the two calls, or is it like creating two separate instances and applying them one after the other? Is it even legal P4 to call a control (or parser) twice?

Here is an example: https://github.com/cornell-netlab/petr4/blob/36862d54cd037fa964e951db6883236c37485342/stf-test/custom-stf-tests/multiple-calls.p4

...and an STF test describing what I'd expect it to do if local variables persist: https://github.com/cornell-netlab/petr4/blob/36862d54cd037fa964e951db6883236c37485342/stf-test/custom-stf-tests/multiple-calls.stf

@mihaibudiu
Copy link
Contributor

Local variables should behave exactly as in functions. The only way to preserve the value is for them to be uninitialized on the second entry. This is undefined behavior. If the spec is not clear about this we have a bug.

@mihaibudiu
Copy link
Contributor

It is perfectly legal to call the same instance twice.
Creating two instances has a different semantics than calling the same instance twice. Creating a new instance creates new tables; calling the same instance twice reuses the same tables (something some architectures may not support).

@jnfoster
Copy link
Contributor

jnfoster commented Mar 17, 2021

What if we replace the local variable with a register of size 1? That is, the body of C is:

register<bit<8>>(1) r;
apply {
  bit<8> x;
  r.read(x,0);
  r.write(0,x+1);
}

@mihaibudiu
Copy link
Contributor

constructor invocation = state allocation.
so there are as many registers as there are instantiations.

@mihaibudiu
Copy link
Contributor

There's a section on the evaluation of P4 programs: https://p4.org/p4-spec/docs/P4-16-v1.2.1.html#sec-p4-abstract-mach

@jnfoster
Copy link
Contributor

Indeed. We understand this, and Petr4 does the right thing.

It does seem to be slightly confusing that local variables declared in a control do not persist across invocations, but other stateful elements do. One could imagine adding a restriction (though this would not be backward compatible) to require all local variables to be declared in the apply block rather than in the preamble that precedes it... the compiler enforces a dual restriction -- you can't invoke constructors in the apply block.

@mihaibudiu
Copy link
Contributor

As I said, for you to take advantage of the variable persistence they need to have a path of execution in which the variables are read before they are written (so they must be uninitialized in the first execution). You could conceive a legal program that carries state between control invocations to make this safe.

@QinshiWang
Copy link
Contributor

So, the initialization of local variables happens on each invocation. But the uninitialized variables are persistent, right? If I write

control C (bool flag, input bit<8> x) {
  bit<8> i;

  apply {
    if (flag) {
      i = 1;
    }
    i = i + 1;
    x = i;
  }
}

control MyIngress(inout hdr_t h, inout metadata meta, inout standard_metadata_t
std_meta) {
    C() c_inst;
    apply {
        c_inst.apply(true, h.f.val);
        c_inst.apply(false, h.f.val);
    }
}

in @hackedy's example, the result will be 3?

@jafingerhut
Copy link
Contributor

My understand of the P4_16 language spec is that all local variables (not externs, but variables) that are uninitialized, begin every execution of the enclosing scope with an unspecified initial value, that might depend upon the target, and the target is free to initialize it with the value that you least wish it had, as far as your program's safety/correctness is concerned.

Your example program could assign any unspecified value to h.f.val during the copy-out of the second call to c_inst.apply().

@mihaibudiu
Copy link
Contributor

Let's say that the meaning of this program is undefined. Your interpretation is possible, but not the only one.
An alternative interpretation is that i is clobbered with a random value every time you invoke c_inst.
Feel free to submit a PR against the spec to make this clear.

@mihaibudiu
Copy link
Contributor

Yes, @jafingerhut is right.
You may be lucky and the unspecified value could be the one you left with last time.

@QinshiWang
Copy link
Contributor

QinshiWang commented Mar 22, 2021

I see. The spec says

The value of a variable is never preserved from one invocation of its enclosing block to the next.

It is a bit confusing the scope of an invocation of a control block include the whole control declaration or only the apply block.

@jafingerhut
Copy link
Contributor

Whether it is the entire control declaration, or only the apply block, calling apply() on a control two times is exiting the entire scope of the entire control, including its apply block, but also everything else defined within the control.

@hackedy
Copy link
Contributor Author

hackedy commented Mar 22, 2021

Whether it is the entire control declaration, or only the apply block, calling apply() on a control two times is exiting the entire scope of the entire control, including its apply block, but also everything else defined within the control.

Except for any stateful objects, including externs and other control instances, which were instantiated within the control. That instantiation happens once, I think.

@jafingerhut
Copy link
Contributor

Note: If you want some value preserved from one apply() call to an instance of a control to the next, you can make it an out parameter to 'return' the value from the first call, and remember that value in the caller and pass it as an in parameter the next time you call apply() on the control. Or it can be a single inout parameter.

@jafingerhut
Copy link
Contributor

Whether it is the entire control declaration, or only the apply block, calling apply() on a control two times is exiting the entire scope of the entire control, including its apply block, but also everything else defined within the control.

Except for any stateful objects, including externs and other control instances, which were instantiated within the control. That instantiation happens once, I think.

Agreed, instantiation of extern objects and tables happens once. They are explicitly defined by the language to be objects that contain their own state, though, even from processing one packet to the next.

@mihaibudiu
Copy link
Contributor

Does anyone in this discussion want to create a PR to make the spec crisper?

@jafingerhut
Copy link
Contributor

I would be happy to write a PR that might help reduce confusion, if I knew the source of the confusion. I might be too close to the spec's details and the way things already are to see it clearly.

Is the basic idea that it is currently confusing that local variables within a parser or control are either:

(a) if they have no initialization expression, e.g. they look something like bit<8> foo;, then they are uninitialized on every invocation of the enclosing scope, and their value is not guaranteed to be any particular value of its type, but it will be some value of its type, perhaps a different one every time the enclosing scope is executed.

(b) if they have an initialization expression, e.g. they look something like bit<8> foo = 7;, then they are guaranteed to be reinitialized to the right hand side expression on every invocation of the enclosing scope.

If statements like those would clear things up for some readers, where in the spec would such statements best go?

@mihaibudiu
Copy link
Contributor

The confusion comes perhaps from the fact that instantiations that happen outside the apply block of a control preserve state across invocations, while variables do not. One could think that there is a difference between variables declared outside and inside the apply block. Perhaps the section on evaluation? Or someplace where the structure of controls is described?

@hackedy
Copy link
Contributor Author

hackedy commented Mar 31, 2021

Thanks everyone for your comments, this was helpful. I'll write a pull request with a clarification.

@mihaibudiu
Copy link
Contributor

If you do it before Monday we'll discuss it at the next design group meeting.

@jnfoster
Copy link
Contributor

This PR subsumed by #1243.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants