Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a fast processor-native bitshift function #52828

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ New library features
* `sizehint!(s, n)` now supports an optional `shrink` argument to disable shrinking ([#51929]).
* New function `Docs.hasdoc(module, symbol)` tells whether a name has a docstring ([#52139]).
* New function `Docs.undocumented_names(module)` returns a module's undocumented public names ([#52413]).
* The functions `unsafe_ashr`, `unsafe_lshr` and `unsafe_shl` are processor-native
bitshifting functions which may be faster than the similar functions `>>`, `>>>`
and `<<` when the shift is not known at compile time.
* Passing an `IOBuffer` as a stdout argument for `Process` spawn now works as
expected, synchronized with `wait` or `success`, so a `Base.BufferStream` is
no longer required there for correctness to avoid data races ([#52461]).
Expand Down
3 changes: 3 additions & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ export
≥,
>>,
>>>,
unsafe_lshr,
unsafe_ashr,
unsafe_shl,
\,
^,
|,
Expand Down
104 changes: 104 additions & 0 deletions base/int.jl
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,110 @@ top_set_bit(x::BitInteger) = 8sizeof(x) - leading_zeros(x)
<=(x::BitUnsigned, y::BitSigned ) = (y >= 0) & (x <= unsigned(y))

## integer shifts ##
"""
unsafe_lshr(x, n)

Unsafe logical right bit shift operator. If `n` is negative, or equal to or
higher than the number of bits in `x`, the resulting value is platform-dependent,
although the function is guaranteed to finish and return a value of the correct type.
Unlike [`unsafe_ashr`](@ref), the `n` top bits of the result are always unset.

See also: [`>>>`](@ref), [`unsafe_ashr`](@ref)

# Examples
```jldoctest
julia> unsafe_lshr(18, 2)
4

julia> unsafe_lshr(Int32(5), 4)
0

julia> unsafe_lshr(Int32(-100), 3)
536870899
```
"""
function unsafe_lshr end

"""
unsafe_ashr(x, n)

Unsafe arithmetic right bit shift operator. If `n` is negative, or equal to or
higher than the number of bits in `x`, the resulting value is platform-dependent,
although the function is guaranteed to finish and return a value of the correct type.

If `x` is nonnegative, the `n` top bits of the result will be unset, if `x` is negative,
they will be set. This implies the sign of `x` will be preserved.

See also: [`>>`](@ref), [`unsafe_lshr`](@ref)

# Examples
```jldoctest
julia> unsafe_ashr(18, 2)
4

julia> unsafe_ashr(Int32(5), 4)
0

julia> unsafe_ashr(Int32(-100), 3)
-13
```
"""
function unsafe_ashr end

"""
unsafe_shl(x, n)

Unsafe left bit shift operator. If `n` is negative, or equal to or
higher than the number of bits in `x`, the resulting value is platform-dependent,
although the function is guaranteed to finish and return a value of the correct type.
The lowest `n` bits of the result are unset.

See also: [`<<`](@ref), [`unsafe_ashr`](@ref)

# Examples
```jldoctest
julia> unsafe_shl(7, 2)
28

julia> unsafe_shl(Int32(1001), 3)
8008

julia> unsafe_shl(Int32(9), 28)
-1879048192
```
"""
function unsafe_shl end

function unsafe_lshr(x::Union{Int8, UInt8, Int16, UInt16}, y::BitInteger)
lshr_int(zext_int(UInt32, x), (y % UInt32) & 0x1f) % typeof(x)
end

function unsafe_lshr(x::T, y::BitInteger) where {T <: Union{Int32, UInt32, UInt64, Int64, UInt128, Int128}}
lshr_int(x, (y % UInt32) & (8*sizeof(T) - 1)) % T
end

function unsafe_shl(x::Union{Int8, UInt8, Int16, UInt16}, y::BitInteger)
shl_int(zext_int(UInt32, x), (y % UInt32) & 0x1f) % typeof(x)
end

function unsafe_shl(x::T, y::BitInteger) where {T <: Union{Int32, UInt32, UInt64, Int64, UInt128, Int128}}
shl_int(x, (y % UInt32) & (8*sizeof(T) - 1)) % T
end

function unsafe_ashr(x::Union{Int8, Int16}, y::BitInteger)
ashr_int(sext_int(UInt32, x), (y % UInt32) & 0x1f) % typeof(x)
end

function unsafe_ashr(x::T, y::BitInteger) where {T <: Union{Int32, Int64, Int128}}
ashr_int(x, (y % UInt32) & (8*sizeof(T) - 1)) % T
end

function unsafe_ashr(x::BitUnsigned, y::BitInteger)
@inline unsafe_lshr(x, y)
end

# For 8-32 bits x, trunc n to u32, & 0x1f, recast back to x
# For 64

# unsigned shift counts always shift in the same direction
>>(x::BitSigned, y::BitUnsigned) = ashr_int(x, y)
Expand Down
3 changes: 3 additions & 0 deletions doc/src/base/math.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ Base.denominator
Base.:(<<)
Base.:(>>)
Base.:(>>>)
Base.unsafe_ashr
Base.unsafe_lshr
Base.unsafe_shl
Base.bitrotate
Base.:(:)
Base.range
Expand Down
20 changes: 20 additions & 0 deletions test/int.jl
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,26 @@ end
end
end

@testset "native bitshifts" failfast=true begin
for T1 in Base.BitInteger_types
nbits = 8*sizeof(T1)
val = 0x1234567890abcdef1234567890abcdef % T1
for T2 in Base.BitInteger_types
for shift in 0:nbits-1
s = T2(shift)
@test unsafe_lshr(val, s) === val >>> s
@test unsafe_ashr(val, s) === val >> s
@test unsafe_shl(val, s) === val << s
end

invalid = nbits + T2(10)
@test typeof(unsafe_lshr(val, invalid)) == typeof(val >>> T2(1))
@test typeof(unsafe_ashr(val, invalid)) == typeof(val >> T2(1))
@test typeof(unsafe_shl(val, invalid)) == typeof(val << T2(1))
end
end
end

@testset "bit rotations" begin
val1 = 0b01100011
@test 0b00011011 === bitrotate(val1, 3)
Expand Down