4 ms·
> I don't think that pointer provenance rears its ugly head here. You are generally free to convert a pointer to an integer and then back, which is what's happe
by wahern 4y ago
> I don't think that pointer provenance rears its ugly head here. You are generally free to convert a pointer to an integer and then back, which is what's happening here
C [optionally] supports converting pointers to intptr_t (or uintptr_t), not to any integer type (even with nominally sufficient width), and certainly not to a floating point type, which is how NaN boxing works.
In CHERI intptr_t requires special treatment by the compiler. Plus, the nominal width of both pointers and intptr_t double in size--128-bits on 64-bit architectures. Most environments don't even have a 128-bit floating point type, even when the hardware might support it. (According to Wikipedia, Fortran is an exception, but it's still uncommon in C and other popular language environments.)
- Dylan16807 4y ago> C [optionally] supports converting pointers to intptr_t (or uintptr_t) Yes it's optional, but it's "generally" supported. > (even with nominally sufficient width) C99 says: "Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type." I'm not entirely sure how I'm supposed to parse that, but it sounds like if the result is in range of an integer then it's supposed to work? I guess you could technically say all results have a zillion added to the actual value? Rules lawyering gets annoying pretty fast. > and certainly not to a floating point type, which is how NaN boxing works NaN boxing works by converting to an integer type first. What it does with the bits afterwards is not important to C, as long as it reconstructs the same integer before converting back.
- lokedhs 4y agoThe part you quoted seems pretty clear. It's implementation defined. That's why it works done on most modern architectures, but you can't rely on it in portable code, as there are certainly architectures where it doesn't.
- kazinator 4y agoimplementation-defined is not "optional". The implementation must provide the conversion and document how it's done; it cannot say that, "oh, it's not supported and the program will refuse to compile, or behave erratically". However some stupid definition of behavior could be given, like converting any pointer to integer results in a reliable 0. C has pointer arithmetic; it must support subtraction of pointers that point into the same array-like object. So even on that implementation, you might perhaps be able to obtain a large-ish array and treat that as a heap in which you can convert pointers to integers by subtracting the base.
- klodolph 4y ago> not to any integer type (even with nominally sufficient width), I’m going to have to say that you’re definitely wrong about that, because when I read the C standard, it seems pretty clear to me. From N2176 (C17 draft) §6.3.2.3: > An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation. > Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type. As I said, there are some implementation-dependent factors here, but it should be clear that casting a pointer to and from an integer is OK, depending on the implementation, and depending on what integer type—and it’s not just limited to intptr types. The C standard guarantees that you can always cast to and from intptr_t / uintptr_t (if it exists), and other casts are implementation-defined. Relying on implementation-defined behavior is ok. There’s nothing here that requires casting to float. You’re passing around a union which has both a float and a uint64_t in it.
- wahern 4y agoThis issue isn't round-tripping integers through object pointers, but round-tripping object pointers through integers.[1] C only guarantees the latter to occur through intptr_t, if at all. And you can't use plain pointers to round-trip integers because they have no observable structure by which you could inspect them for these user space tagging tricks. So you're stuck with intptr_t. Also, none of your points pertain to NaN boxing in particular. NaN boxing literally requires floating point types, and nothing in the standard permits floating point types to be used this way. NaN boxing fundamentally relies upon undefined behavior. All the modern hysteria regarding undefined behavior notwithstanding, however, clearly people can manage to implement workable solutions even in the face of what the standard makes undefined. But such de facto allowances are the first to be discarded as situations change, otherwise they would have been codified as part of a standard. As the article describes, most of what can be achieved from an optimization point with NaN boxing can be achieved by smuggling floats through pointers or integers, rather than vice-versa, but strictly speaking NaN boxing is especially problematic. [1] The standard says "Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined." But in CHERI 1 bit of capability data (the most critical bit!) is stored outside addressable memory and by design cannot be represented as part of an integer, except intptr_t, which as I described requires special compiler treatment. Moreover, this special treatment might not be free, obviating the performance advantages of type punning tricks. I forget the details, but IIRC in some cases a call into the kernel is actually required to preserve C's existing conversion semantics. And intptr_t semantics in CHERI more generallly likely present barriers to other kinds of compiler optimizations, even ignoring the fact that our memory saving type punning hack doesn't actually save memory anymore on account of both pointers and intptr_t being double the width of every other type in the union.