You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Simplify codegen of mixed-type checked integer addition and subtraction (#15878)
Code generation for mixed-type uses of overflow-checked primitive integer addition or subtraction is currently somewhat convoluted, in particular when the receiver is signed and the argument unsigned, or vice versa. The compiler would create an intermediate integer type that has one more bit than the operands, e.g. `i9` or `i33`; most LLVM targets do not like these types, and this leads to [some rather unsightly LLVM IR that is hard to optimize](https://godbolt.org/z/h7EP71W51).
This PR is a complete overhaul of the way these additions and subtractions are emitted. There are now multiple specialized code paths depending on whether the two operand types have the same signedness or width. The following Crystal snippet illustrates how each code path could be expressed equivalently in terms of other primitive calls in native Crystal:
```crystal
fun i8_add_u8(p1 : Int8, p2 : UInt8) : Int8
p1_biased = (p1 ^ Int8::MIN).to_u8!
result = p1_biased + p2 # same-type, checked
result.to_i8! ^ Int8::MIN
end
fun u16_add_i8(p1 : UInt16, p2 : Int8) : UInt16
p1_biased = p1.to_i16! ^ Int16::MIN
result = i16_add_i8(p1_biased, p2) # checked, see below
(result ^ Int16::MIN).to_u16!
end
fun i8_add_u16(p1 : Int8, p2 : UInt16) : Int8
p1_biased = (p1 ^ Int8::MIN).to_u8!
result = u8_add_u16(p1_biased, p2) # checked, see below
result.to_i8! ^ Int8::MIN
end
fun i8_add_i16(p1 : Int8, p2 : Int16) : Int8
p1_ext = p1.to_i16!
result = p1_ext &+ p2
result.to_i8 # checked
end
# the actual optimal call sequence is slightly different,
# probably due to some short-circuit evaluation issue
fun u8_add_u16(p1 : UInt8, p2 : UInt16) : UInt8
p2_trunc = p2.to_u8 # checked
p1 + p2_trunc # same-type, checked
end
fun i16_add_i8(p1 : Int16, p2 : Int8) : Int16
p2_ext = p2.to_i16!
p1 + p2_ext # same-type, checked
end
```
([Before](https://godbolt.org/z/b5vdnscnK) and [after](https://godbolt.org/z/qa5avE9cW) on Compiler Explorer)
The gist here is that mixed-signedness operations are transformed into same-signedness ones by applying a bias to the first operand and switching its signedness, using a bitwise XOR. For example, `-0x80_i8..0x7F_i8` maps linearly to `0x00_u8..0xFF_u8`, and vice-versa. The same-signedness arithmetic operation that follows will overflow if and only if the original operation does. The result is XOR'ed again afterwards, as the bias action is the inverse of itself. This is the trick that allows mixed-type addition or subtraction without resorting to arcane integer bit widths.
0 commit comments