Rearranging across the interface
Rearranging across the interface
Posted Feb 26, 2025 16:32 UTC (Wed) by farnz (subscriber, #17727)In reply to: Rearranging across the interface by Wol
Parent article: Rewriting essential Linux packages in Rust
No; I'm saying that if the compiler doesn't even know that this is an interface boundary, why would it bother detecting that you've moved code across the boundary in a fashion that's safe when statically linked, but not when dynamically linked?
Put concretely, in the private module raw_vec.rs (none of which is exposed as an interface boundary), I move a check from shrink_unchecked to shrink; how is the compiler supposed to know that this is not a safe movement to make, given that shrink is the only caller of shrink_unchecked? Further, how it is supposed to know that moving a check from shrink to shrink_unchecked is safe? And, just to make it lovely and hard, how is it supposed to distinguish "this check is safe to move freely" from "this check must not move"?
And note that "checks" and "security fixes" look exactly the same to the compiler; some code has changed. How is the compiler supposed to distinguish a "good" change from a "bad" change?
Posted Feb 26, 2025 21:43 UTC (Wed)
by Wol (subscriber, #4433)
[Link] (3 responses)
Because if the whole aim of this is to create a dynamic library, the compiler NEEDS to know this is an interface boundary, no?
Cheers,
Posted Feb 27, 2025 10:44 UTC (Thu)
by farnz (subscriber, #17727)
[Link] (2 responses)
Note that when making this judgement call, it can't just look at things like "is this moving a check across an internal boundary", since some moves across an internal boundary are safe, nor can you condition it on removing a check from inside the boundary (since I may remove an internal check that is guaranteed to be true since all the inline functions that can call this have always done an equivalent check, and I'm no longer expecting more inline functions without the check).
Posted Feb 27, 2025 14:07 UTC (Thu)
by Wol (subscriber, #4433)
[Link] (1 responses)
If an external application cannot see the boundary, then it's not a boundary! So you'd need to include the definition of all the Ts in Vec<T> you wanted to export, but the idea is that the crate presents a frozen interface to the outside world, and what goes on inside the crate is none of the caller's business. So internal boundaries aren't boundaries.
Cheers,
Posted Feb 27, 2025 14:12 UTC (Thu)
by farnz (subscriber, #17727)
[Link]
To get the sort of boundary you're describing, we do static linking and carefully hand-crafted interfaces for plugins. That's the state of play today, for everything from assembly through C to Agda and Idrs; the goal, however, is to dynamically link, which means that we need to go deeper. And then we have a problem, because the moment you go deeper, your boundaries stop applying, thanks to inlining.
Rearranging across the interface
Wol
But then you're getting into a mess around defining what is, and is not, a safe code change inside the ABI boundary. If you do make the internals of a crate (not the exported interface) the ABI boundary, you're now in a position where the compiler has to make a judgement call - "is this change inside the internals of a library a bad change, or a good change?".
Rearranging across the interface
Rearranging across the interface
Wol
No, for performance reasons. We inline parts of our libraries (even in C, where the inlined parts go in the .h file) into their callers because the result of doing so is a massive performance boost from the optimizer - which can do things like reason "hey, len can't be zero here, so I can eliminate the code that handles the empty list case completely".
Rearranging across the interface