Skip to content

Check for out/inout bindings aliased with uses #5318

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

ChrisDodd
Copy link
Contributor

This is compiler support for p4lang/p4-spec#1370.

Basically, the idea is that we want to forbid "aliasing" between out/inout arguments and things that are accessed directly in the called thing. This simplifies backends in that the don't have to detect such cases and special case them to avoid problematic behavior. The fact that there have been a number of issues in the past related to this aliasing suggests that alllowing it is problematic both from implementation complexity and user understanding. Having a clear compile-time error message (rather than confusing runtime behavior) is much better for all involved.

The implementation here is fairly straight-forward. There are two passes here

  • The first pass analyzes callable things that contain code (currently actions, functions, controls, and parsers) and identifies all the symbols that they reference from their enclosing scope, along with the expressions used to access them (important for when the object only accesses a few fields from a larger object).
  • The second pass analyzes call sites, to determine if any actual argument passed as an out or inout parameter "overlaps" with any of the things found in the first pass of the callee.

Currently it only looks at things on a per-field basis. Accesses to arrays are assumed to access any element of the array, and slices of fields are not tracked per-bit to see if they are disjoint or overlap. The code could be extended to track constant array indexes (so two different constant indexes would not overlap) and bit slices.

As can be seen there are a number of existing test cases that trigger this (new) error. Two of them have been moved to P4_16_errors, as what they were doing was highly questionable. The others have been modified to not have this error as they seem to be testing unrelated things.

@kfcripps kfcripps requested review from asl, jphurl and oleg-ran-amd June 12, 2025 03:17
@kfcripps kfcripps added core Topics concerning the core segments of the compiler (frontend, midend, parser) breaking-change This change may break assumptions of compiler back ends. labels Jun 12, 2025
@asl
Copy link
Contributor

asl commented Jun 12, 2025

@ChrisDodd Is this going to support also aliasing cases at the caller side? E.g.

struct S {
  bit<16> field
}

action foo(inout S, out bit<16>) { ... }

S s;
foo(S, s.field)

@ChrisDodd
Copy link
Contributor Author

@ChrisDodd Is this going to support also aliasing cases at the caller side? E.g.

struct S {
  bit<16> field
}

action foo(inout S, out bit<16>) { ... }

S s;
foo(S, s.field)

I think that should already be an error -- using the same object for two different out or inout args in the same call.

@asl
Copy link
Contributor

asl commented Jun 12, 2025

I think that should already be an error -- using the same object for two different out or inout args in the same call.

I might be missing something, but the following code compiles fine for me on main:

struct S {
  bit<16> field;
}

action foo(inout S s, inout bit<16> f) {
  s.field = 42;
  f = f + s.field;
}

action bar() {
 S s = { 10 };
 foo(s, s.field);
}

Is this an error with this PR?

@fruffy
Copy link
Collaborator

fruffy commented Jun 12, 2025

I think that should already be an error -- using the same object for two different out or inout args in the same call.

Iirc this was allowed because the spec has precise description of the expected behavior. There was a similar issue with #2176

@ChrisDodd
Copy link
Contributor Author

I really don't like allowing multiple writes to the same object in a single call, even though the spec has been worded to allow it. IMO that's s spec flaw we should discuss in the WG.

I'm of a mind to add the check here (even though the spec says it should be ok), but it could also be "fixed" by inserting copies around the call (which, if desirable, should be done in a later, midend pass). In any case this needs more discussion.

- pass that finds captured expressions in callable (symbols defined
  outside the callable)
- modify some failing tests to not have the error

Signed-off-by: Chris Dodd <[email protected]>
@asl
Copy link
Contributor

asl commented Jun 16, 2025

I really don't like allowing multiple writes to the same object in a single call, even though the spec has been worded to allow it. IMO that's s spec flaw we should discuss in the WG.

Can this be solved in a way done with -fstrict-aliasing in C/C++ compilers? E.g. have an option that enforces particular aliasing pattern? E.g. in C/C++ we can optionally enforce strict aliasing rules. In P4 we can optionally disallow any object aliasing this way?

@ChrisDodd
Copy link
Contributor Author

ChrisDodd commented Jun 16, 2025

Can this be solved in a way done with -fstrict-aliasing in C/C++ compilers? E.g. have an option that enforces particular aliasing pattern? E.g. in C/C++ we can optionally enforce strict aliasing rules. In P4 we can optionally disallow any object aliasing this way?

-fstrict-aliasing in C/C++ just allows the compiler to assume that lvalues of different types don't alias. Since we don't have pointers or pointer casts in P4, there is no way for lvalues of different types to alias (a value cast gives an rvalue -- not something that can be used as an out or inout argument). A slice lvalue is an explicit alias of (some of) the bits of a larger value. So it does have a "different type" but allowing the compiler to assume it does not alias would be even more confusing, I think.

The main thing we're trying to avoid in P4 is "undefined behavior" -- where the spec doesn't say what happens, but an error diagnostic at compile time is not required. The existing spec currently avoids undefined behavior by specifying a strict behavior (copying argument left-to-right), while IMO it would be better to specify it as a compile time error.

@asl
Copy link
Contributor

asl commented Jun 16, 2025

-fstrict-aliasing in C/C++ just allows the compiler to assume that lvalues of different types don't alias. Since we don't have pointers or pointer casts in P4, there is no way for lvalues of different types to alias (a value cast gives an rvalue -- not something that can be used as an out or inout argument). A slice lvalue is an explicit alias of (some of) the bits of a larger value. So it does have a "different type" but allowing the compiler to assume it does not alias would be even more confusing, I think.

I know quite well what strict aliasing rules are, thanks :) As I mentioned, my idea is to optionally enforce no-alias rules. Certainly, the name of the option would be different, but essentially this might relax copy-in / copy-out semantics giving stronger guarantees to the compiler (in C terms everything would be restrict-qualified).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking-change This change may break assumptions of compiler back ends. core Topics concerning the core segments of the compiler (frontend, midend, parser)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants