Part 1
One really powerful optimization that the compiler wants to do whenever it can is removing accesses to memory. For example, consider this code:Code Block swift func setPointerAndPrint(ptr1: UnsafeMutablePointer<Int>) { ptr1.pointee = 1 print(ptr1.pointee) }
The compiler can see very clearly that, by the time we reach print(ptr1.pointee), its value could only possibly be 1, so it can turn that line into print(1) instead without changing your program’s behavior. This is a small savings for this particular bit of code, but in the general case, this kind of optimization can enable much more powerful ones. For example, if this rule turns a guard !ptr1.pointee.isMultiple(of: 2) into guard !1.isMultiple(of: 2), then the compiler could determine that the check will never fail and delete the whole guard statement. Small, individual optimizations like this can snowball into very large performance differences.
But this only works if the compiler can tell that none of the code between those lines could change ptr1.pointee. For example, if the code looked like this:
Code Block swift func setPointerAndPrint(ptr1: UnsafeMutablePointer<Int>) { ptr1.pointee = 1 someFunctionInAnotherModule() print(ptr1.pointee) }
Then Swift can’t turn print(ptr1.pointee) into print(1), because it doesn’t know what someFunctionInAnotherModule() might do—it could set ptr1.pointee to a different value. So it doesn’t remove that memory access.
One especially vexing aspect of this problem is called “aliasing”. Aliasing is when two pointers point to the same memory, so a write to one pointer will change the value read from the other. Maybe 95% of the time, two pointers involved in the same operation don’t alias each other—but if the compiler simply assumes that they don’t alias, the code it generates will behave incorrectly in the remaining 5% of cases.
For example, consider this function:
Code Block swift func setPointersAndPrint(ptr1: UnsafeMutablePointer<Int>, ptr2: UnsafeMutablePointer<Int>) { ptr1.pointee = 1 ptr2.pointee = 2 print(ptr1.pointee) }
Almost all of the time, the write to ptr2.pointee won’t affect ptr1.pointee, so the compiler would like to convert print(ptr1.pointee) into print(1). But you could pass the same pointer to both parameters, and then that change would produce an incorrect result, so the compiler can’t perform that optimization. It’s rather irritating to give up that performance improvement.
But what about this function?
Code Block swift func setPointersAndPrint(ptr1: UnsafeMutablePointer<Int>, ptr2: UnsafeMutablePointer<UInt>) { ptr1.pointee = 1 ptr2.pointee = 2 print(ptr1.pointee) }
It is at least theoretically possible to make ptr1 and ptr2 point to the same address, but it doesn’t really make sense to do that, because the two are of different types! Maybe there’s a 0.001% situation where someone is intentionally doing something really weird, but we’d really like that speedup in the remaining 99.999% of situations, and we don’t want to lose such a big win for such a niche situation. 5% is us being careful; 0.001% is us sulking in our tents.
So we don’t sulk. Instead, we say that, if two typed pointers do not have compatible pointee types, they are not allowed to alias, and allow the Swift optimizer to assume this.
To be continued...