It seems to me (and this is somewhat off-the-cuff) that compilers have another option: integers which are cast from pointers have provenance.
That is, provenance is a taint: you can't clean it off through casting. So casting from pointer means the user can do integer-things like addition, but it means the compiler can't do integer-things like constant folding.
I don't think that works. Consider the following (contrived) program:
char* q[1] = {0};
int iq = (uintptr_t)q;
int ip = 0;
while (iq>0) {
iq--;
ip++;
}
char* p = (char*) ip;
You can make this more efficient (albeit no less contrived) by iterating through iq bit-wise.
Less contrived would be sending a pointer through some IPC mechanism (allowing it to be used as an opaque handle externally, while the program will blindly dereference it when it is passed back in); or even passing it within a program across compilation units.
If you treat provenance like a taint, you need to track every way it could be propagated, and that does not seem solvable.
So what are the optimizations here? iq is tainted, you can't hoist the loop out. You've said "I'm doing a weird thing here with an integer which used to be a pointer, you have to assume that it involves memory and treat it more like a pointer than an integer"
It seems to me (and this is somewhat off-the-cuff) that compilers have another option: integers which are cast from pointers have provenance.
That is, provenance is a taint: you can't clean it off through casting. So casting from pointer means the user can do integer-things like addition, but it means the compiler can't do integer-things like constant folding.