Static type checking helps us be more confident that our software does what we think it does. But it can’t see everything, and this post was originally going to share a neat C++ feature that might have helped me be a little more confident about the code I’m writing. However just after I started writing this I found that it’s not necessary and there is (doh, I should have known) a nice C way to get the same check. However I still want to write it because it might be handy to remember this in the future.
I’m trying to add a counter to SpiderMonkey to count how many nursery, and tenured allocations there are. I’ve implemented this for the non-JIT paths and now it’s time to implement the same for the JIT paths. Here’s the code for the JIT during a nursery allocation:
add32(Imm32(1), AbsoluteAddress(zone->addressOfNurseryAllocCount()));
This code isn’t what runs at runtime (kinda) it’s part of the JIT and is executed when we want to generate code that performs a nursery allocation. In other words, it doesn’t perform the allocation itself, the machine code it generates will.
This causes the JIT to write instruction(s) (usually 1) that perform a 32-bit add. They add the immediate value 1, to the value contained at the address provided by the call zone->addressOfNurseryAllocCount(). This returns a pointer to a uint32_t value. However the AbsoluteAddress constructor will cast this to a void pointer, before writing the 32bit add instruction(s) using it.
This means that if in 6 months time I decide that we need 64 bit counters, or want to save so much memory that 16 bit counters would be better. That if the type of CompileZone::addressOfNurseryAllocCount() changed from uint32_t* to uint64_t* we’d have a problem (yes we would, popular platforms like x86 are little endian and will add the wrong bytes together).
So I wanted some kind of check here that if someone did make this change, they’d get a compiler error and change the add32 to an add64 for example. So I used:
static_assert(mozilla::IsSame<uint32_t*, decltype(zone->addressOfNurseryAllocCount())>::value, "JIT expects this to be a 32bit counter"); add32(Imm32(1), AbsoluteAddress(zone->addressOfNurseryAllocCount()));
Note that mozilla::IsSame is like std::is_same, it uses the template system to substitute in a different value for its value member depending on if the substituted types are the same. If they are, then the value is non-zero and the static_assert is accepted. But if the type of this function were to change then the assertion would fail, exactly what we want!
But there’s a simpler way. Just create a local variable of the desired type and assign to it first.
uint32_t *allocCount = zone->addressOfNurseryAllocCount(); add32(Imm32(1), AbsoluteAddress(allocCount));
The compilation will fail if the coercion implied by the assignment isn’t possible or safe. However, this won’t prevent all coercions (and hence my confusion), a uint32_t may be coerced to a uint64_t, but not a uint32_t* to a uint64_t*. So the simple solution is all we need here.
I’ve been through much of the existing JIT code today and made this type of improvement in many places Bug 1476500.
I wonder if dependent types can ensure that a code generator generates (more) type correct code?