Identifiers declared as `const`: local compile-time known or compile time known?
The working draft spec makes the distinction between local compile-time known and compile-time known values. (in section 18.1) As said in the spec, the main difference is that compile-time known values include constructor parameters while local compile-time known values do not.
And the spec lists "identifiers declared as constants using the const keyword" as a compile-time known value. Also in section 11.2, the spec mentions that the initializer for a constant declaration must be a compile-time known value.
But shouldn't identifiers declared as const be local compile-time known instead?
Below is an example code from the Petr4 sandbox.
const int MAX_HOPS = 10;
const int STANDARD = 0x00;
const int HOPS = 0x01;
typedef standard_metadata_t std_meta_t;
header type_t {
bit<8> tag;
}
header hop_t {
bit<8> port;
bit<8> bos;
}
header standard_t {
bit<8> src;
bit<8> dst;
}
struct headers_t {
type_t type;
hop_t[MAX_HOPS] hops;
standard_t standard;
}
Here, note that constant MAX_HOPS is used as the size for a header stack hops. And the spec mentions that "for a header stack hs[n], the term n ... must be a local compile-time known value that is a positive integer." (section 7.2.3)
So, if we consider MAX_HOPS as a compile-time known value (which is a superset of local compile-time known value), the typechecker should reject the above program.
I don't think this is what is intended in the spec, i.e., shouldn't identifiers declared as const should be local compile-time known instead?
Maybe the same as this issue? https://github.com/p4lang/p4-spec/issues/1290
Certainly if the initializer expression for a const contained a non-local compile time known value, e.g. a constructor parameter, then I don't see how that const could be local compile-time known.
Hmm yes, I don't think the current p4c compiler allows a constructor parameter to be an initializer for a constant.
#include <core.p4>
#include <v1model.p4>
typedef standard_metadata_t std_meta_t;
header type_t {
bit<8> tag;
}
header hop_t {
bit<8> port;
bit<8> bos;
}
header standard_t {
bit<8> src;
bit<8> dst;
}
struct headers_t {
type_t type;
standard_t standard;
}
struct meta_t { }
parser MyParser(packet_in pkt, out headers_t hdr, inout meta_t meta, inout std_meta_t std_meta) {
state start { transition accept; }
}
control MyVerifyChecksum(inout headers_t hdr, inout meta_t meta) { apply { } }
control MyComputeChecksum(inout headers_t hdr, inout meta_t meta) { apply { } }
control MyIngress(inout headers_t hdr, inout meta_t meta, inout std_meta_t std_meta)(bit<8> carg) {
const bit<8> c = carg;
apply { }
}
control MyEgress(inout headers_t hdr, inout meta_t meta, inout std_meta_t std_meta) { apply { } }
control MyDeparser(packet_out pkt, in headers_t hdr) { apply { } }
V1Switch(MyParser(), MyVerifyChecksum(), MyIngress(8w42), MyEgress(), MyComputeChecksum(), MyDeparser()) main;
This results in:
lctk.p4(36): [--Werror=invalid] error: carg: Cannot evaluate initializer for constant
const bit<8> c = carg;
^^^^
So to check if we are on the same page: I believe identifiers declared as const should be (1) local compile-time known values and also (2) not compile-time known values.
Sure, but since we are talking about the language specification here, which is sometimes more general than what p4c accepts, it makes sense to ask whether an initialization expression for a const should allow constructor parameters, or not. If it should, then clearly those const identifiers cannot be local compile-time known.
If I remember correctly, the thinking that led to the creation of the notion of local compile-time known values was the desire to be able to type-check, say, controls, without having the rest of the program available. That's why the constants, declared outside were not included into the list.
I do find this very counter-intuitive as well and this does diminish the usefulness of P4 constants significantly.
@vgurevich your recollection is correct. However, the type checker should still be able to use const declarations in the scope of the control in question. So I think this was an oversight, not deliberate. IMHO, we should make the proposed change.
That would definitely be nice, @jnfoster !
I think this makes sense. A constant declaration is fundamentally a known thing at early type checking, unlike the constructor parameter, which is bound only at instantiation. So I think it makes sense to be able to declare constants that are local compile-time known values (and then use them in things like bit<(E)> and header stack sizes.
That said, I wonder if we should also have ability to declare (non-local-)compile-time known constant variables. I.e. given an constructor argument, should I be able to create an expression with it and save the result into a constant variable. If so, then we either need two kinds of constness indicator (const and something else, where it is not clear to me which one should be the local variant) or we need to figure out (at type checking time probably) for each const variable if it is local compile-time known or just compile-time known.
Personally, I would suggest that both use cases are valid (while I don't have example for the non-local one). And I would prefer having to different declaration forms as it would make it clear what was the author's intention and it would make the errors more readable (as they would refer to the place where the non-local constant is assigned to declared-local-const variable, as opposed to error referring to a use of a constant that happens to be derived to not be local compile-time known).
Consensus from the LDWG meeting is to make the spec more conservative to match what p4c is doing and make the proposed change. If a compelling use case comes up for compile-time known values versus local-compile-time known values we can revisit.
@vlstill @jaehyun1ee Would either of you be interested in writing a pull request with changes to the spec for this? Based on the discussion at the 2024-Sep-09 LDWG meeting, I believe the change would basically be to add to the list of local compile-time known values all identifiers declared as const. I think it would help when doing so, to make it explicit that the initialization expression for the value of such a const must contain only local compile-time known values.
@jafingerhut Thank you for the discussion at LDWG, PTAL #1307