Question: Why isn't null_pointer_is_valid
a module-level property? (original) (raw)
Hey folks, I’m curious about the design choice behind the null_pointer_is_valid
attribute and why it’s not a module-level property. I’m trying to understand in what scenario a null pointer would be considered valid in one function but not in another, even though both functions are in the same module. Thanks.
lenary March 24, 2025, 3:59pm 2
LTO merges modules, and it’s perfectly valid to merge two different IR modules that were compiled with different flags. Usually this applies to things like optimisation levels or target features (i.e. different -march
flag) but I don’t see why it shouldn’t apply to that attribute.
shiltian March 24, 2025, 4:27pm 3
So this is more like an optimization attribute instead of target feature related?
lenary March 24, 2025, 9:47pm 4
I’m not sure. In ⚙ D78862 [IR] Convert null-pointer-is-valid into an enum attribute there was some discussion about it going into the DataLayout (which is per-module) rather than an attribute, but that an attribute would still be required for overriding in a per-function manner. I think @jdoerfert will understand the direction for this attribute a bit better.
arsenm March 25, 2025, 12:34am 5
No, it’s a security hack that works in a wrong by default direction. For the intended use it should probably be viral
nikic May 2, 2025, 1:03pm 6
I agree that null_pointer_is_valid should be a module flag rather than a function attribute. “Can an allocated object exist at address 0?” is not a property of an individual function. And in particular the question “Can a global variable have address 0?” needs a module-level rather than function-level answer, as it is a module-level entity.
Having a module flag for this is still compatible with LTO – we just need to define the appropriate merging behavior (which is “if any module has null_pointer_is_valid, the result module also has null_pointer_is_valid”).
Looking back at RFC: Implementing -fno-delete-null-pointer-checks in clang, it seems like the suggestion to use a function attribute instead of a module flag came from @efriedma-quic, maybe they have some more insight into this choice?
Yes, but…
The real-world usage of this feature is only partly for situations where an object can actually be allocated at the zero address.
The other use for this feature is to pretend that an object might be allocated at address zero and that a null pointer dereference might succeed. Even though it never actually will, at runtime. The goal is just to avoid the treatment of null dereference as known-UB – so that e.g. a null pointer comparison which comes after a dereference won’t be deleted as dead code.
For that “pretending” use-case, having the behavior enabled/disabled per function is quite reasonable.
nikic May 5, 2025, 8:51pm 8
Thanks for the context. I agree that doing it per-function is sufficient for that use-case – but is it necessary?
-fno-delete-null-pointer-checks
is specified per translation unit, which would correspond to the module level in LLVM. If we don’t support toggling this per-function, then the only difference would be that some post-link optimizations may be lost if TUs with different -fno-delete-null-pointer-checks
are mixed. Is that what we’re concerned about?
By default, I prefer not to use module-level attributes in cases where there might be mismatches, because mismatches tend to be painful to handle. Either we fail to link, or we implicitly change behavior in a translation unit that didn’t request the change. Function attributes don’t have that cliff: things usually just work.
For null_pointer_is_valid specifically, it’s maybe not that painful to just force everything into that mode, but it’s still not great.
And yes, null_pointer_is_valid is mostly used in environments like the Linux kernel where null pointers aren’t actually valid.