[HN Gopher] Tree Borrows
       ___________________________________________________________________
        
       Tree Borrows
        
       Author : zdw
       Score  : 376 points
       Date   : 2025-07-09 14:40 UTC (8 hours ago)
        
 (HTM) web link (plf.inf.ethz.ch)
 (TXT) w3m dump (plf.inf.ethz.ch)
        
       | pvg wrote:
       | The Stacked Borrows mentioned had threads in 2020 and 2018
       | 
       | https://news.ycombinator.com/item?id=22281205
       | 
       | https://news.ycombinator.com/item?id=17715399
        
       | kibwen wrote:
       | Recent blog post from Ralf Jung providing some extra context:
       | https://www.ralfj.de/blog/2025/07/07/tree-borrows-paper.html
       | 
       | Bonus: recent talk from Ralf Jung on his group's efforts to
       | precisely specify Rust's operational semantics in executable form
       | in a dialect of Rust: https://youtube.com/watch?v=yoeuW_dSe0o
        
       | wavemode wrote:
       | From the paper:
       | 
       | > The problem with unsafe code is that it can do things like
       | this:                   fn main() {             let mut x = 42;
       | let ptr = &mut x as *mut i32;             let val = unsafe {
       | write_both(&mut *ptr, &mut *ptr) };
       | println!("{val}");         }
       | 
       | No it can't? Using pointers to coexist multiple mutable
       | references to the same variable is undefined behavior. Unless I'm
       | just misunderstanding the point they're trying to make here.
        
         | seritools wrote:
         | "can do things" in this case doesn't mean "is allowed to do
         | things".
         | 
         | "Unsafe code allows to express the following, which is UB:"
        
         | ehsanu1 wrote:
         | I believe that's exactly the point: it's too easy to violate
         | constraints like not allowing multiple mutable references.
         | Unsafe is meant for cases where the validity of the code is
         | difficult to prove with rust's lifetime analysis, but can be
         | abused to do much more than that.
        
         | pavpanchekha wrote:
         | The point of this work is to pin down the exact boundaries of
         | undefined behavior. Certainly the code above is _accepted by
         | the Rust compiler_ , but it also _breaks rules_. What rules? In
         | essence, we know that:
         | 
         | - Anything accepted by the borrow checker is legal
         | 
         | - Unsafe can express illegal / undefined behavior
         | 
         | - There's some set of rules, broader than what the borrow
         | checker can check, that is still legal / defined behavior
         | 
         | The goal of this line of work is to precisely specify that set
         | of rules. The outlines are clear (basically, no writable
         | pointers should alias) but the details (interior pointers,
         | invalidation of iterators, is it _creating_ or _using_ bad
         | pointers that 's bad, etc) are really hard. The previous paper
         | in this series, on Stacked Borrows, was simpler but more
         | restrictive, and real-world unsafe code often failed its rules
         | (while still seeming correct). Tree Borrows is broader and
         | allows more while still being provably safe.
        
           | ralfj wrote:
           | > allows more while still being provably safe.
           | 
           | Note that we have not yet proven this. :) I hope to one day
           | prove that every program accepted by the borrow checker is
           | compatible with TB, but right now, that is only a (very well-
           | tested) conjecture.
        
             | sunshowers wrote:
             | Hi Ralf! Congrats to you all for the PLDI distinguished
             | paper award.
        
               | ralfj wrote:
               | Thanks :-)
        
         | oconnor663 wrote:
         | You're already getting a lot of replies, and I don't want to
         | pile on, but I think the clearest way to see the intent there
         | is at the start of the following paragraph:
         | 
         | > Given that aliasing optimizations are something that the Rust
         | compiler developers clearly want to support, we need some way
         | of "ruling out" counterexamples like the one above from
         | consideration.
        
         | ralfj wrote:
         | > Using pointers to coexist multiple mutable references to the
         | same variable is undefined behavior.
         | 
         | Yes, but which exact rule does it violate? What is the exact
         | definition that says that it is UB? Tree Borrows is a proposal
         | for exactly such a definition.
         | 
         | "code can do things like this" here means "you can write this
         | code and compile it and run it and it will do something, and
         | unless we have something like Tree Borrows we have no argument
         | for why there would be anything wrong with this code".
         | 
         | You seem to have already accepted that we need something like
         | Tree Borrows (i.e., we should say code like this is UB). This
         | part of the paper is arguing why we need something like Tree
         | Borrows. :)
        
         | GolDDranks wrote:
         | > Unless I'm just misunderstanding the point they're trying to
         | make here.
         | 
         | You misunderstand the word "can". Yes, you can, in unsafe code,
         | do that. And yes, that is undefined behaviour ;)
         | 
         | https://play.rust-lang.org/?version=stable&mode=debug&editio...
        
       | pil0u wrote:
       | Just realised that one of the author, Neven Villani, is Cedric
       | Villani's (Fields Medal 2010) son. Apples don't fall far from the
       | tree, indeed.
        
         | tandr wrote:
         | > Apples don't fall far from the tree, indeed.
         | 
         | And one could say that they borrow from the tree some of their
         | qualities. Sorry, couldn't resist.
        
         | Yoric wrote:
         | Hey, I used to have an office close to the dad's :)
         | 
         | That's before he went into politics, though.
        
       | fuhsnn wrote:
       | I wonder if Rust or future PL would evolve into allowing multiple
       | borrow checker implementations with varying characteristics
       | (compile speed, runtime speed, algorithm flexibility, etc.) that
       | projects can choose from.
        
         | speed_spread wrote:
         | I cannot imagine how that would work. You couldn't combine code
         | that expect different borrowing rules to be applied. You'd
         | effectively be creating as many sub-dialects as there are
         | borrow checker implementations.
        
           | vlovich123 wrote:
           | FWIW technically the rules are the same. How they go about
           | proving that the rules are upheld for a program is what would
           | be different.
        
         | umanwizard wrote:
         | What's wrong with the compile or runtime speed of the current
         | one?
        
         | pjmlp wrote:
         | We already have that by having multiple approaches via affine
         | types (what Rust uses), linear types, effects, dependent types,
         | formal proofs.
         | 
         | All have different costs and capabilities across
         | implementation, performance and developer experience.
         | 
         | Then we have what everyone else besides Rust is actually going
         | for, the productivity of automatic resource management
         | (regardless of how), coupled with one of the type systems
         | above, only for performance critical code paths.
        
           | LelouBil wrote:
           | I would love some sort of affine types in languages like
           | Kotlin, it just makes cleaner code organization in my
           | opinion.
           | 
           | Doesn't matter if it's purely "syntaxical" because the
           | language is garbage collected, just the fact of specifying
           | what owns what and be explicit about multiple references is
           | great imo.
           | 
           | Some sort of effects systems can already be simulated with
           | Kotlin features too.
           | 
           | Programming language theory is so interesting!
        
           | ChadNauseam wrote:
           | > affine types (what Rust uses)
           | 
           | I'd just like to interject for a moment. What you're
           | referring to as "affine types", is in fact, Uniqueness Types.
           | The difference has to do with how they interact with
           | unrestricted types. In Rust, these "unrestricted types" are
           | references (which can be used multiple times due to
           | implementing Copy).
           | 
           | Uniqueness types allow functions to place a constraint on the
           | caller ("this argument cannot be aliased when you pass it to
           | me"), but places no restriction on the callee. This is useful
           | for Rust, because (among other reasons) if a value is not
           | aliased you can free it and be sure that you're not leaving
           | behind references to freed data.
           | 
           | Affine types are the opposite - they allow the caller to
           | place a restriction on the callee ("I'm passing you this
           | value, but you may use it at most once"), which is not
           | something possible to express in Rust's type system, because
           | the callee is always free to create a reference from its
           | argument and pass that reference to multiple functions..
        
             | ralfj wrote:
             | I would say it is perfectly accurate to call Rust's type
             | system affine. At its core, "affine" means that the type
             | system has exchange and weakening but not contraction, and
             | that exactly characterizes Rust's type system. See <https:/
             | /math.stackexchange.com/questions/3356302/substructur...>
             | for an explanation of what those terms mean (that's in the
             | context of a logic, but it's the same for type systems via
             | the Curry-Howard correspondence).
             | 
             | This is often explained via the "do not use more than once
             | rule", but that's not the actual definition, and as your
             | example shows, following that simplified explanation to the
             | letter can cause confusion.
             | 
             | > because the callee is always free to create a reference
             | from its argument and pass that reference to multiple
             | functions..
             | 
             | Passing a reference is not the same thing as passing the
             | actual value, so this does not contradict affinity.
        
               | ChadNauseam wrote:
               | > Passing a reference is not the same thing as passing
               | the actual value, so this does not contradict affinity.
               | 
               | I agree that passing a reference is not the same thing as
               | passing the actual value. If it were, there would really
               | be no point to references. However, it does contradict
               | affinity. Specifically, the fact that multiple references
               | can be created from the same value, combined with the
               | properties of references, contradicts affinity.
               | 
               | > At its core, "affine" means that the type system has
               | exchange and weakening but not contraction, and that
               | exactly characterizes Rust's type system.
               | 
               | Well, the rust type system certainly does support
               | contraction, as I can use a reference multiple times. So
               | what is that if not contraction? It seems like rust at
               | least does support contraction for references.
               | 
               | But in practice, having absolutely no contraction is not
               | a very useful definition of affine, because no practical
               | programming language would ever satisfy it. It prohibits
               | too much and the language would not even be turing
               | complete. Instead, there is usually an "affine world" and
               | an "exponential world". (Exponential meaning
               | "unrestricted" values that you can do whatever you want
               | with). And the convention is that values can go from the
               | exponential world to the affine world, but not back. So a
               | function taking an affine value can be passed any value,
               | but must use in in an affine way, and meanwhile but a
               | function taking an exponential (unrestricted) value can
               | only be passed exponential and not an affine value.
               | 
               | If you don't believe me, you can try using linear
               | haskell, and notice that a function taking a linear
               | argument can be passed a non-linear argument, but not the
               | other way around.
               | 
               | If you interpret Rust's type system this way, it's
               | natural to interpret references as exponentials. But
               | references have the opposite convention. You can go from
               | owned values to references, but not the other way around,
               | which is precisely the opposite situation as the
               | convention around linear/affine type systems. Because
               | these systems feel very different to use and enforce very
               | different properties, I do think it's important that we
               | have separate names for them rather than referring to
               | both as "affine". And the usual name for the rust-like
               | system is "uniqueness types", see https://docs.idris-
               | lang.org/en/latest/reference/uniqueness-t... or
               | https://en.wikipedia.org/wiki/Uniqueness_type .
        
               | ralfj wrote:
               | > Well, the rust type system certainly does support
               | contraction, as I can use a reference multiple times. So
               | what is that if not contraction? It seems like rust at
               | least does support contraction for references.
               | 
               | Good question! For shared references, the answer is that
               | they are `Copy`, so they indeed have contraction.
               | Affinity just means that contraction is not a universal
               | property, but some types/propositions may still have
               | contraction. For mutable references, you can't _actually_
               | use them multiple times. However, there is a desugaring
               | phase going on before affinity gets checked, so uses of
               | mutable references `r` get replaced by ` &mut *r`
               | everywhere. That's not using contraction, it's not
               | literally passing `r` somewhere, it is calling a
               | particular (and interesting) operating on `r`
               | ("reborrowing").
               | 
               | Rust is not just an affine system, it is an affine system
               | extended with borrowing. But I think it is still entirely
               | fair to call it an affine system, for the simple fact
               | that the language will prevent you from "using" a
               | variable twice. "reborrowing" is just not a case of
               | "using", it is its own special case with its own rules.
               | 
               | > But in practice, having absolutely no contraction is
               | not a very useful definition of affine,
               | 
               | Obviously Rust has a class of "duplicable" types, called
               | `Copy`. That's besides the point though.
               | 
               | > If you interpret Rust's type system this way, it's
               | natural to interpret references as exponentials.
               | 
               | Why would that be natural? Mutable references are not
               | even duplicable, so what you say makes little sense for
               | references in general. Maybe you mean shared references
               | -- those are just an example of a duplicable type.
               | 
               | Rust doesn't have a modality in its type system that
               | would make every type duplicable, so there is no
               | equivalent to exponentials. (In particular, `&T` isn't a
               | modality around `T`. It's a different type, with a
               | different representation. And as you noted, even if it
               | were a modality, it wouldn't correspond to exponentials.)
               | 
               | But a type system can be affine/linear without having
               | exponentials so I don't understand the point of this
               | remark.
               | 
               | Uniqueness types seem to be all about how many references
               | there are to a value. You can use linear/affine types to
               | enforce such a uniqueness property (and that is indeed
               | what Rust does), but that doesn't take away from the fact
               | that you have a linear/affine type system.
               | 
               | > Because these systems feel very different to use and
               | enforce very different properties,
               | 
               | I can't talk about the "feel" as I never programmed in an
               | affine language (other than Rust ;), but in terms of the
               | properties, what Rust does is extremely closely related
               | to affine logics: the core property being enforced is
               | that things do not get duplicated. My model of Rust,
               | RustBelt, uses an affine separation logic to encode the
               | properties of the Rust type system, and there's a lot of
               | overlap between separation logic and linear logic. So we
               | have further strong evidence here that it makes perfect
               | sense to call Rust an affine language.
        
               | caim wrote:
               | The main point of Affine logic is that it doesn't allow
               | contraction, and the Rust type system does allow
               | different forms of contraction. How exactly is Rust an
               | "affine language"?
               | 
               | Also, the claims about Curry-Howard correspondence are
               | wrong. It does not prove that rust is an affine language:
               | https://liamoc.net/forest/loc-000S/index.xml
               | 
               | But Swift DOES have affine types with the "Non copyable"
               | types that doesn't allow contraction.
        
               | hollerith wrote:
               | Rust has types that don't allow contraction (i.e., being
               | used twice), too: e.g., String, vectors and boxes.
               | 
               | Their being that way is essential for the borrow checker
               | to provide the memory-safety guarantees it provides.
        
               | caim wrote:
               | Yep, that's true. But multiple immutable shared
               | references are a form of contraction, while mutable
               | references are actually affine.
               | 
               | Swift doesn't have references like Rust, and you can't
               | even have unsafe raw pointers to variables without
               | producing a dangling pointer, but this makes Swift more
               | restrictive and less powerful than Rust.
        
             | caim wrote:
             | Yeah, that makes sense. The Rust type system isn't "affine"
             | as in affine logic. Rust allows different forms of
             | contraction, which affine logic strictly prohibits.
             | 
             | And some people like to claim that the Curry-Howard
             | correspondence proves something about their type system,
             | but this is only true for dependently typed languages.
             | 
             | And the proofs aren't about program behavior.
             | 
             | See, https://liamoc.net/forest/loc-000S/index.xml
        
         | 0x000xca0xfe wrote:
         | As I understand it the borrow checker only has false negatives
         | but no false positives, correct?
         | 
         | Maybe a dumb question but couldn't you just run multiple
         | implementations in parallel threads and whichever finishes
         | first with a positive result wins?
        
           | vlovich123 wrote:
           | This presumes that checking composes which may not if you
           | have orthogonal checker implementations. You might end up
           | risking accepting an invalid program because part of it is
           | valid under one checker, part under another, but the
           | combination isn't actually valid. But maybe that's not
           | actually possible in practice.
        
             | afdbcreid wrote:
             | Borrow checking is function-local, so if the opsem model is
             | the same and you run the different checkers per-function,
             | there is no such risk.
        
         | sunshowers wrote:
         | That would result in ecosystem splitting, which isn't great.
        
         | pornel wrote:
         | Rust already supports switching between borrow checker
         | implementations.
         | 
         | It has migrated from a scope-based borrow checker to non-
         | lexical borrow checker, and has next experimental Polonius
         | implementation as an option. However, once the new
         | implementation becomes production-ready, the old one gets
         | discarded, because there's no reason to choose it. Borrow
         | checking is fast, and the newer ones accept strictly more
         | (correct) programs.
         | 
         | You also have Rc and RefCell types which give you greater
         | flexibility at cost of some runtime checks.
        
         | Ericson2314 wrote:
         | What you actually want is the underlying separation logic, so
         | you can precisely specify function preconditions and prove mid-
         | function conditions, and the the optomizer can take all those
         | "lemmas" and go hog-wiled, right up to but _not past_ what is
         | allowed by the explicitly stated invariants.
         | 
         | "Rust", in this context, is "merely" "the usual invariants that
         | people want" and "a suite of optimizations that assume those
         | usual invariants, but not more or less".
        
       | Voultapher wrote:
       | Amazing work, I remember reading the Tree Borrows spec? on
       | Nevin's website a couple years ago and being thoroughly impressed
       | by how it solves some pretty gnarly issue quite elegantly. And in
       | my experience [1] [2] it does indeed allow for sensible code that
       | is illegal under Stacked Borrows.
       | 
       | [1] https://github.com/Voultapher/sort-research-
       | rs/blob/main/wri... Miri column
       | 
       | [2] https://github.com/rust-
       | lang/rust/blob/6b3ae3f6e45a33c2d95fa...
        
       | vollbrecht wrote:
       | Hmm i just tested out the claim that the following rust code
       | would be rejected ( Example 4 in the paper).
       | 
       | And it seams to not be the case on the stable compiler version?
       | fn write(x: &mut i32) {*x = 10}              fn main() {
       | let x = &mut 0;           let y = x as *mut i32;
       | //write(x); // this should use the mention implicit twophase
       | borrow           *x = 10; // this should not and therefore be
       | rejected by the compiler           unsafe {*y = 15 };       }
        
         | Arnavion wrote:
         | Stacked borrows is miri's runtime model. Run it under miri and
         | you will see the error reported for the `*x = 10;` version but
         | not the `write(x);` version - "Undefined Behavior: attempting a
         | write access using [...] but that tag does not exist in the
         | borrow stack for this location".
         | 
         | rustc itself has no reason to reject either version, because y
         | is a *mut and thus has no borrow/lifetime relation to the &mut
         | that x is, from a compile-time/typesystem perspective.
        
           | vollbrecht wrote:
           | Ah that make sense. Thanks for clarifying.
        
       | jcalvinowens wrote:
       | > On the one hand, compilers would like to exploit the strong
       | guarantees of the type system--particularly those pertaining to
       | aliasing of pointers--in order to unlock powerful intraprocedural
       | optimizations.
       | 
       | How true is this really?
       | 
       | Torvalds has argued for a long time that strict aliasing rules in
       | C are more trouble than they're worth, I find his arguments
       | compelling. Here's one of many examples:
       | https://lore.kernel.org/all/CAHk-=wgq1DvgNVoodk7JKc6BuU1m9Un...
       | (the entire thread worth reading if you find this sort of thing
       | interesting)
       | 
       | Is Rust somehow fundamentally different? Based on limited
       | experience, it seems not (at least, when unsafe is involved...).
        
         | ralfj wrote:
         | I would agree that C's strict aliasing rules are terrible. The
         | rules we are proposing for Rust are very different. They are
         | both more useful for compilers and, in my opinion, less onerous
         | for programmers. We also have an actual in-language opt-out:
         | use raw pointers. And finally, we have a tool you can use to
         | check your code.
         | 
         | But in the end, it's a trade-off, like everything in language
         | design. (In life, really. ;) We think that in Rust we may have
         | found a new sweet spot for this kind of optimizations. Time
         | will tell whether we are right.
        
           | kookamamie wrote:
           | Agreed about C's aliasing rules. Fortran had a better set of
           | defaults.
        
           | NobodyNada wrote:
           | As someone who has been writing a lot of unsafe Rust (mostly
           | in an embedded context), I'm thrilled about and thankful for
           | the work that you, your students, and the opsem team are
           | doing.
           | 
           | When you're working with anything below the application
           | level, C's confusing and underspecified rules about UB are
           | almost impossible to keep track of, especially when it comes
           | to aliasing and volatile/MMIO. The spec is so difficult to
           | read and full of complicated cross-references that to
           | actually get a practical answer you have to look for a random
           | Stack Overflow post that may or may not have a correct
           | interpretation of the spec, and may or may not address your
           | specific problem.
           | 
           | Rust right now feels a lot harder to work with, because the
           | spec isn't done. When you have a concrete question about a
           | piece of code, like "is this conversion from an &mut to a
           | *mut and back sound", and you try to look for documentation
           | on it, you get either "Nobody knows, Rust aliasing model
           | isn't defined"; a hand-wavy explanation that is not rigorous
           | or specific; or a model like Stack Borrows or Tree Borrows
           | that's defined a little too formally for easy digestion :)
           | 
           | But when I really started digging, I realized just how much
           | _cleaner_ Rust 's semantics are. References aren't actually
           | _hard_ , Tree Borrows basically boils down to "while an &mut
           | reference is live, you can only access the value through
           | pointers or references derived from that reference". Pointer
           | operations have straightforward semantics, there's no
           | confusing notions of typed memory, and no UB "just because"
           | for random things like integer overflow. It's just so much
           | less complicated to understand than C's abstract machine.
           | 
           | I'm really looking forward to things like MiniRust, and to an
           | aliasing model making it into the Reference / other
           | documentation, because at that point I feel like unsafe Rust
           | will be _way_ easier to write confidently and correctly than
           | C.
           | 
           | Congrats on the publication, and thanks again for the work
           | you all have put into this.
        
         | Asooka wrote:
         | While I can't name the product I work on, we also use -fno-
         | strict-aliasing. The problem with these optimisations is that
         | they can only be done safely if you can prove aliasing never
         | happens, which is equivalent to solving the halting problem in
         | C++. In Rust I suspect the stronger type system can actually
         | prove that aliasing doesn't happen in select cases. In any
         | case, I can always manually do the optimisations enabled by
         | strict aliasing in hot code, but I can never undo a customer
         | losing data due to miscompilation.
        
           | pornel wrote:
           | > actually prove that aliasing doesn't happen in select cases
           | 
           | In the safe subset of Rust it's guaranteed in _all_ cases.
           | Even across libraries. Even in multi-threaded code.
        
             | oconnor663 wrote:
             | To elaborate on that some more, safe Rust can guarantee
             | that mutable aliasing never happens, without solving the
             | halting program, because it forbids some programs that
             | could've been considered legal. Here's an example of a
             | function that's allowed:                   fn foo() {
             | let mut x = 42;             let mut mutable_references =
             | Vec::new();             let test: bool = rand::random();
             | if test {                 mutable_references.push(&mut x);
             | } else {                 mutable_references.push(&mut x);
             | }         }
             | 
             | Because only one if/else branch is ever allowed to execute,
             | the compiler can see "lexically" that only one mutable
             | reference to `x` is created, and `foo` compiles. But this
             | other function that's "obviously" equivalent doesn't
             | compile:                   fn bar() {             let mut x
             | = 42;             let mut mutable_references = Vec::new();
             | let test: bool = rand::random();             if test {
             | mutable_references.push(&mut x);             }
             | if !test {                 mutable_references.push(&mut x);
             | // error: cannot borrow `x` as mutable more than once at a
             | time             }         }
             | 
             | The Rust compiler doesn't do the analysis necessary to see
             | that only one of those branches can execute, so it
             | conservatively assumes that both of them can, and it
             | refuses to compile `bar`. To do things like `bar`, you have
             | to either refactor them to look more like `foo`, or else
             | you have to use `unsafe` code.
        
         | steveklabnik wrote:
         | While both involve aliasing, C's strict aliasing and Rust's
         | aliasing are two different things. Rust pretty explicitly did
         | not adopt the C style.
         | 
         | C's aliasing is based on type alone, hence its other name "type
         | based alias analysis" or TBAA.
        
         | dzaima wrote:
         | Rust's aliasing rules are very different from C's.
         | 
         | In C you have a nuclear `restrict` that in my experience does
         | anything only when applied to function arguments across clang &
         | gcc, and type-based aliasing which is both not a generally-
         | usable thing (don't have infinite different copies of the
         | int64_t type (and probably wouldn't want such either)), and
         | annoying (forces using memcpy if you want to reinterpret to a
         | different type).
         | 
         | Whereas with Rust references you have finely-bounded lifetimes
         | and spans and mutability, and it doesn't actually care about
         | the "physical" types, so it is possible to reinterpret memory
         | as both `&mut i32`/`&i32` and `&mut i64`/`&i64` and switch
         | between the two for the same memory, writing/reading halves
         | of/multiple values by the most bog standard Rust safe reads &
         | writes, as long as the unsafe abstraction never gives you
         | overlapping `&mut` references at the same time, or split a
         | `&mut` into multiple non-overlapping `&mut`s.
        
         | jandrewrogers wrote:
         | Strict aliasing rules are useful conditional on them being
         | sufficiently expressive and sensible, otherwise they just
         | create pointless headaches that require kludgy workarounds or
         | they are just disabled altogether. I don't think there is much
         | disagreement that C strict aliasing rules are pretty broken.
         | There is no reason a language like Rust can't be designed with
         | much more sensible strict aliasing rules. Even C++ has invested
         | in providing paths to more flexibility around strict aliasing
         | than C provides.
         | 
         | But like Linus, I've noticed it doesn't seem to make much
         | difference outside of obvious narrow cases.
        
         | ivanbakel wrote:
         | >How true is this really?
         | 
         | I'd be interested to see a more thorough analysis, but there is
         | a simple way to gauge this - rip out all the parts of the
         | compiler where aliasing information is propagated to LLVM, and
         | see what happens to performance.
         | 
         | I found a claim that noalias contributes about 5% performance
         | improvement in terms of runtimes[0], though the data is
         | obviously very old.
         | 
         | https://github.com/rust-lang/rust/issues/54878#issuecomment-...
        
         | jcranmer wrote:
         | Take anything Linus says about compilers with a grain of salt--
         | he writes OS kernels, not compilers, and those are pretty
         | different domains.
         | 
         | Alias analysis is _extremely_ important for getting good
         | performance these days--but it should also be remembered that
         | the biggest benefits accrue from the simplest heuristics (like
         | two loads that use the same SSA value as the pointer must alias
         | each other). In LLVM terms, that 's BasicAA: a collection of
         | very simple heuristics that primarily amounts to "if we can
         | track down the allocation sites of objects, we can definitively
         | resolve these alias queries; otherwise, we don't know."
         | 
         | The real question that you're trying to ask, though, is what is
         | the value of alias analyses that go beyond the most basic,
         | obvious tests. At the point where the alias queries are no
         | longer trivial to solve, then it's generally the case that what
         | you can do as a result of those queries also shrinks
         | dramatically, pretty much to looking for code motion hazards,
         | and the benefits you get from that are much reduced. One of the
         | experiments I would like to do is measure the total speedup
         | you'd get from a theoretically perfect alias analysis, and my
         | guess is that it's somewhere in the 20% range even on non-HPC
         | code like the Linux kernel [1].
         | 
         | [1] This doesn't account for the heroic optimizations, such as
         | data-layout transformations, that you wouldn't attempt to write
         | without a very high-quality alias analysis. But since we
         | already know that alias analysis doesn't exist in practice,
         | we're not going to attempt those optimizations anyways, so it's
         | not worth including such stuff in prospective speed gains.
        
           | oconnor663 wrote:
           | Here's another data point: https://lobste.rs/s/yubalv/pointer
           | s_are_complicated_ii_we_ne...
           | 
           | > I spoke to Apple folks when their compiler team switched
           | the default to strict aliasing. They reported that it made
           | key workloads 5-10% faster and the fixes were much easier to
           | do and upstream than I would have expected. My view of
           | -fstrict-aliasing at the time was that it was a flag that let
           | you generate incorrect code that ran slightly faster. They
           | had actual data that convinced me otherwise.
        
         | tliltocatl wrote:
         | It is mostly useful on arrays/numeric code, probably next to
         | useless otherwise. Numerics people was the ones who sponsored
         | much of compiler/optimization work in the first place, that's
         | how strict aliasing came to be.
        
           | dzaima wrote:
           | I don't think the usefulness is that skewed towards numerics?
           | 
           | Both clang/llvm and gcc can do alias checking at runtime if
           | they can't at compile-time, which makes loops vectorizable
           | without alias info, at the cost of a bit of constant overhead
           | for checking aliasing. (there's the exception of gather loads
           | though, where compile-time aliasing info is basically
           | required)
           | 
           | And on the other hand there's good potential for benefit to
           | normal code (esp. code with layers of abstractions) - if you
           | have a `&i32`, or any other immutable reference, it's pretty
           | useful for compiler to be able to deduplicate/CSE
           | loads/computations from it from across the whole function
           | regardless of what intermediate writes to potentially-other
           | things there are.
        
       | gavinhoward wrote:
       | This looks excellent. I will probably implement this model for my
       | own language.
        
       | Nurbek-F wrote:
       | It can't be a dejavu. I keep seeing this post every 2-3 months...
        
         | steveklabnik wrote:
         | The paper is years in the making. This is it finally being
         | published.
        
       | olddustytrail wrote:
       | _Tree Borrows_
       | 
       | "I want my fuckin' money back."
       | 
       | "Hoom, hmm, let us not be hasty!"
       | 
       | "You got 48 hours to deliver or the sapling gets it, Treebeard."
        
       ___________________________________________________________________
       (page generated 2025-07-09 23:00 UTC)