3 ms·
I read the Fil-C overview, and I was confused by one thing: how does Fil-C handle integer-to-pointer conversions? Rust has the new strict provenance API that i
by amluto 2y ago
I read the Fil-C overview, and I was confused by one thing: how does Fil-C handle integer-to-pointer conversions? Rust has the new strict provenance API that is somewhat explicitly designed to avoid a need to materialize a pointer capability from just an integer, but C and C++ have no such thing. So if the code does:
int deref(uintptr_t p)
{
return *(int*)p;
}
Does this fail unconditionally? Or is there some trick by which it can succeed if p is valid? And, if the latter is the case, then how is memory safety preserved?
edit: I found zptrtable and this construct:
https://github.com/pizlonator/pizlonated-quickjs/commit/258a4a291fd0f080614e5b345528478c31e51705#diff-45f1ae674139f993bf8a99c382c1ba4863272a6fec2f492d76d7ff1b2cfcfbe2R5181 https://github.com/pizlonator/pizlonated-quickjs/commit/258a...
The latter seems to indicate that:
(int*)((uintptr_t)p + 1)
is at least a semi-reliable way to increment p by one. I guess this is a decent way to look like C and to keep a widely-used pattern functional. Rust’s with_addr seems like a more explicit and less magical way to accomplish the same thing. If Fil-C really takes off, would you want to add something like with_addr? Is allowing the pair of conversions on the same line of code something that can be fully specified and can be guaranteed to compile correctly such that it never accidentally produces a pointer with no capability?
- pizlonator 2y agoYour deref function will fail, yeah. The pair of conversions is guaranreed to always produce a pointer with a capability. That’s how I implemented it and it’s low-tech enough that it could be specified.
- amluto 2y agoHow far can the pair of conversions be pushed? Will this work: (int*)(f((uintptr_t)p)) Does it matter if f is inline? Could someone implement Rust’s with_addr as: (int*)((uintptr_t)p, addr) FWIW, I kind of like zptrtable, and I think Fil-C sounds awesome. And I’m impressed that you were able to port large code bases with as few changes as it seems to have taken.
- pizlonator 2y agoYour first example will hilariously work if `f` is inline and simple enough and optimizations are turned on. I'm not sure I like that, so I might change that. I'd like to only guarantee the you get a capability in cases where that guarantee holds regardless of optimization (and I could achieve that with some more compiler hacking). Not sure about the semantics of with_addr. But note that you can do this in Fil-C: char* p = ...; uintptr_t i = ...; p -= (uintptr_t)p; // now p is NULL but still has its original capability p += i; // now p points to whatever i's address was, but has p's original capability I have a helper like that called `zmkptr` (it's just an inline function that does exactly the above).
- amluto 2y agowith_addr is basically that, but with a name and some documentation: https://doc.rust-lang.org/std/primitive.pointer.html#method.with_addr https://doc.rust-lang.org/std/primitive.pointer.html#method.... As I understand it, Rust added this in part for experiments with CHERI but mostly for miri. Interestingly, the implementation of with_addr is very similar to your code. How do you handle cases where there are multiple possible sources of the capability? For example: int *a = …, *b = …; (int*)(((uintptr_t)a + (uintptr_t)b) << 2) I’m not sure I would allow this into any code I maintain, but still. There’s also the classic xor-list, and someone has probably done it like: struct node { struct node *p; }; struct node *next(struct node *here, struct node *prev) { return (struct node *)((uintptr_t)here ^ (uintptr_t)prev); } And this needs to either result in a compiler error or generate some kind of code. Rust’s with_addr wins points for being explicit and unambiguous. It obviously loses points for not being C. And Rust benefits here from all of this being in the standard library and from some of the widely-available tooling (miri) getting mad if code doesn’t use it. I can imagine a future Fil-Rust project doing essentially the same thing as Fil-C except starting with Rust code. It might be interesting to see how the GC part would interact with the rest of the language.
- pizlonator 2y agoMy compiler analysis says that if you have two possible pointers that a capability might come from, like in your first example, then you get no capability at all. I think that's a better semantics than picking some capability at random. If you want to be explicit about where the capability comes from, use `zmkptr`.