mirror of https://github.com/rust-lang/nomicon
				
				
				
			
						commit
						794c2d6302
					
				| @ -0,0 +1,135 @@ | |||||||
|  | # Aliasing | ||||||
|  | 
 | ||||||
|  | First off, let's get some important caveats out of this way: | ||||||
|  | 
 | ||||||
|  | * We will be using the broadest possible definition of aliasing for the sake | ||||||
|  | of discussion. Rust's definition will probably be more restricted to factor | ||||||
|  | in mutations and liveness. | ||||||
|  | 
 | ||||||
|  | * We will be assuming a single-threaded, interrupt-free, execution. We will also | ||||||
|  | be ignoring things like memory-mapped hardware. Rust assumes these things | ||||||
|  | don't happen unless you tell it otherwise. For more details, see the | ||||||
|  | [Concurrency Chapter](concurrency.html). | ||||||
|  | 
 | ||||||
|  | With that said, here's our working definition: variables and pointers *alias* | ||||||
|  | if they refer to overlapping regions of memory. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Why Aliasing Matters | ||||||
|  | 
 | ||||||
|  | So why should we care about aliasing? | ||||||
|  | 
 | ||||||
|  | Consider this simple function: | ||||||
|  | 
 | ||||||
|  | ```rust | ||||||
|  | fn compute(input: &u32, output: &mut u32) { | ||||||
|  |     if *input > 10 { | ||||||
|  |         *output = 1; | ||||||
|  |     } | ||||||
|  |     if *input > 5 { | ||||||
|  |         *output *= 2; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | We would *like* to be able to optimize it to the following function: | ||||||
|  | 
 | ||||||
|  | ```rust | ||||||
|  | fn compute(input: &u32, output: &mut u32) { | ||||||
|  |     let cached_input = *input; // keep *input in a register | ||||||
|  |     if cached_input > 10 { | ||||||
|  |         *output = 2;  // x > 10 implies x > 5, so double and exit immediately | ||||||
|  |     } else if cached_input > 5 { | ||||||
|  |         *output *= 2; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | In Rust, this optimization should be sound. For almost any other language, it | ||||||
|  | wouldn't be (barring global analysis). This is because the optimization relies | ||||||
|  | on knowing that aliasing doesn't occur, which most languages are fairly liberal | ||||||
|  | with. Specifically, we need to worry about function arguments that make `input` | ||||||
|  | and `output` overlap, such as `compute(&x, &mut x)`. | ||||||
|  | 
 | ||||||
|  | With that input, we could get this execution: | ||||||
|  | 
 | ||||||
|  | ```rust,ignore | ||||||
|  |                     //  input ==  output == 0xabad1dea | ||||||
|  |                     // *input == *output == 20 | ||||||
|  | if *input > 10 {    // true  (*input == 20) | ||||||
|  |     *output = 1;    // also overwrites *input, because they are the same | ||||||
|  | } | ||||||
|  | if *input > 5 {     // false (*input == 1) | ||||||
|  |     *output *= 2; | ||||||
|  | } | ||||||
|  |                     // *input == *output == 1 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Our optimized function would produce `*output == 2` for this input, so the | ||||||
|  | correctness of our optimization relies on this input being impossible. | ||||||
|  | 
 | ||||||
|  | In Rust we know this input should be impossible because `&mut` isn't allowed to be | ||||||
|  | aliased. So we can safely reject its possibility and perform this optimization. | ||||||
|  | In most other languages, this input would be entirely possible, and must be considered. | ||||||
|  | 
 | ||||||
|  | This is why alias analysis is important: it lets the compiler perform useful | ||||||
|  | optimizations! Some examples: | ||||||
|  | 
 | ||||||
|  | * keeping values in registers by proving no pointers access the value's memory | ||||||
|  | * eliminating reads by proving some memory hasn't been written to since last we read it | ||||||
|  | * eliminating writes by proving some memory is never read before the next write to it | ||||||
|  | * moving or reordering reads and writes by proving they don't depend on each other | ||||||
|  | 
 | ||||||
|  | These optimizations also tend to prove the soundness of bigger optimizations | ||||||
|  | such as loop vectorization, constant propagation, and dead code elimination. | ||||||
|  | 
 | ||||||
|  | In the previous example, we used the fact that `&mut u32` can't be aliased to prove | ||||||
|  | that writes to `*output` can't possibly affect `*input`. This let us cache `*input` | ||||||
|  | in a register, eliminating a read. | ||||||
|  | 
 | ||||||
|  | By caching this read, we knew that the the write in the `> 10` branch couldn't | ||||||
|  | affect whether we take the `> 5` branch, allowing us to also eliminate a | ||||||
|  | read-modify-write (doubling `*output`) when `*input > 10`. | ||||||
|  | 
 | ||||||
|  | The key thing to remember about alias analysis is that writes are the primary | ||||||
|  | hazard for optimizations. That is, the only thing that prevents us | ||||||
|  | from moving a read to any other part of the program is the possibility of us | ||||||
|  | re-ordering it with a write to the same location. | ||||||
|  | 
 | ||||||
|  | For instance, we have no concern for aliasing in the following modified version | ||||||
|  | of our function, because we've moved the only write to `*output` to the very | ||||||
|  | end of our function. This allows us to freely reorder the reads of `*input` that | ||||||
|  | occur before it: | ||||||
|  | 
 | ||||||
|  | ```rust | ||||||
|  | fn compute(input: &u32, output: &mut u32) { | ||||||
|  |     let mut temp = *output; | ||||||
|  |     if *input > 10 { | ||||||
|  |         temp = 1; | ||||||
|  |     } | ||||||
|  |     if *input > 5 { | ||||||
|  |         temp *= 2; | ||||||
|  |     } | ||||||
|  |     *output = temp; | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | We're still relying on alias analysis to assume that `temp` doesn't alias | ||||||
|  | `input`, but the proof is much simpler: the value of a local variable can't be | ||||||
|  | aliased by things that existed before it was declared. This is an assumption | ||||||
|  | every language freely makes, and so this version of the function could be | ||||||
|  | optimized the way we want in any language. | ||||||
|  | 
 | ||||||
|  | This is why the definition of "alias" that Rust will use likely involves some | ||||||
|  | notion of liveness and mutation: we don't actually care if aliasing occurs if | ||||||
|  | there aren't any actual writes to memory happening. | ||||||
|  | 
 | ||||||
|  | Of course, a full aliasing model for Rust must also take into consideration things like | ||||||
|  | function calls (which may mutate things we don't see), raw pointers (which have | ||||||
|  | no aliasing requirements on their own), and UnsafeCell (which lets the referent | ||||||
|  | of an `&` be mutated). | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
					Loading…
					
					
				
		Reference in new issue