diff --git a/NEWS.md b/NEWS.md index 34e69535374b0..41f3cafe741a3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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]). diff --git a/base/exports.jl b/base/exports.jl index 92525b85c7635..0680ad22ab948 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -199,6 +199,9 @@ export ≥, >>, >>>, + unsafe_lshr, + unsafe_ashr, + unsafe_shl, \, ^, |, diff --git a/base/int.jl b/base/int.jl index 61576d4360835..bb52503829327 100644 --- a/base/int.jl +++ b/base/int.jl @@ -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) diff --git a/doc/src/base/math.md b/doc/src/base/math.md index cff841d991880..ca5e64b249ede 100644 --- a/doc/src/base/math.md +++ b/doc/src/base/math.md @@ -34,6 +34,9 @@ Base.denominator Base.:(<<) Base.:(>>) Base.:(>>>) +Base.unsafe_ashr +Base.unsafe_lshr +Base.unsafe_shl Base.bitrotate Base.:(:) Base.range diff --git a/test/int.jl b/test/int.jl index f79bc5a9781d0..ce71a46e9c9ce 100644 --- a/test/int.jl +++ b/test/int.jl @@ -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)