#
# extensions of FixedVector
#

import Base: setindex, circshift, circshift!, reverse!, convert

export setindex, unsafe_circshift, unsafe_circshift!

"""
    setindex(v::AbstractFixedVector{N,T}, x, i::Integer) where {N,T} -> FixedVector{N,T}

Substitute `x` for the `i`-th component of `v` and return the result. The vector `v` is not modified.

See also `Base.setindex`, [`addindex`](@ref addindex(::AbstractFixedVector, ::Any, ::Integer)).
"""
setindex(::AbstractFixedVector, ::Any, ::Integer)

@inline function setindex(v::AbstractFixedVector{N,T}, x, i::Integer) where {N,T}
    @boundscheck checkbounds(v, i)
    if ishwtype(T) && N-1 <= typemax(uinttype(T))
        setindex_hw(v, convert(T, x), i)
    else
        i1 = i % SmallLength
        t = ntuple(Val(N % SmallLength)) do j
            j == i1 ? convert(T, x) : v[j]
        end
        FixedVector{N,T}(t)
    end
end

@generated function setindex_hw(v::AbstractFixedVector{N,T}, x::T, i::Integer) where {N,T}
    H = hwtype(T)
    L = llvm_type(H)
    U = uinttype(T)
    LU = llvm_type(U)
    w = join((string(LU, ' ', k) for k in 1:N), ", ")
    ir = """
        %x0 = insertelement <$N x $L> poison, $L %1, i8 0
        %xv = shufflevector <$N x $L> %x0, <$N x $L> poison, <$N x i32> zeroinitializer
        %i0 = insertelement <$N x $LU> poison, $LU %2, i8 0
        %iv = shufflevector <$N x $LU> %i0, <$N x $LU> poison, <$N x i32> zeroinitializer
        %f  = icmp eq <$N x $LU> <$w>, %iv
        %c  = select <$N x i1> %f, <$N x $L> %xv, <$N x $L> %0
        ret <$N x $L> %c
    """
    quote
        b = Base.llvmcall($ir, NTuple{N, VecElement{$H}}, Tuple{NTuple{N, VecElement{$H}}, $H, $U}, vec(v.t), hwvalue(x), i % $U)
        FixedVector(unvec(T, b))
    end
end

"""
    addindex(v::AbstractFixedVector{N,T}, x, i::Integer) where {N,T} -> FixedVector{N,T}

Add `x` to the `i`-th component of `v` and return the result. The vector `v` is not modified.

See also [`setindex`](@ref setindex(::AbstractFixedVector, ::Any, ::Integer)).
"""
@propagate_inbounds function addindex(v::AbstractFixedVector, x, i::Integer)
    v + setindex(zero(v), x, i)
end

"""
    $(@__MODULE__).padtail(v::AbstractFixedVector{N,T}, i::Integer, x = default(T)) where {N,T} -> FixedVector{N,T}

Replace the elements of `v` after the `i`-th position by `x` and return the new vector.
Providing an out-of-bounds index `i` does not produce an error.

See also [`$(@__MODULE__).default`](@ref).

# Example
```jldoctest
julia> v = FixedVector{4,Int}(1:4);

julia> $(@__MODULE__).padtail(v, 2)
4-element FixedVector{4, $Int}:
 1
 2
 0
 0

julia> $(@__MODULE__).padtail(v, 2, -1)
4-element FixedVector{4, $Int}:
  1
  2
 -1
 -1
```
"""
function padtail(v::AbstractFixedVector{N,T}, i::Integer, x) where {N,T}
    i = i % SmallLength
    t = ntuple(Val(N % SmallLength)) do j
        ifelse(j <= i, v[j], convert(T, x))
    end
    FixedVector{N,T}(t)
end

@inline function padtail(v::AbstractFixedVector{N,T}, i::Integer) where {N,T}
    ishwtype(T) ? padtail_hw(v, i) : padtail(v, i, default(T))
end

@inline @generated function padtail_hw(v::FixedVector{N,T}, n::Integer) where {N,T}
    H = hwtype(T)
    M = llvm_type(H)
    L = 8*sizeof(T)
    if log2(N) >= L
        L = bitsize(SmallLength)
    end
    U = Symbol(:UInt, L)
    s = join(("i$L $k" for k in 0:N-1), ", ")
    ir = """
        %b = insertelement <$N x i$L> poison, i$L %1, i8 0
        %c = shufflevector <$N x i$L> %b, <$N x i$L> poison, <$N x i32> zeroinitializer
        %d = icmp ult <$N x i$L> <$s>, %c
        %r = select <$N x i1> %d, <$N x $M> %0, <$N x $M> zeroinitializer
        ret <$N x $M> %r
        """
    quote
        @inline
        b = Base.llvmcall($ir, NTuple{N, VecElement{$H}}, Tuple{NTuple{N, VecElement{$H}}, $U}, vec(v), n % $U)
        FixedVector{N,T}(unvec(T, b))
    end
end

@inline @generated function fixedvector_range(::Val{N}, x::T, s::T) where {N,T}
    LT = llvm_type(T)
    t = join((string("$LT ", k) for k in 0:N-1), ", ")
    ir = """
        %s  = insertelement <$N x $LT> poison, $LT %1, i8 0
        %sv = shufflevector <$N x $LT> %s, <$N x $LT> poison, <$N x i32> zeroinitializer
        %u  = mul <$N x $LT> %sv, <$t>
        %x  = insertelement <$N x $LT> poison, $LT %0, i8 0
        %xv = shufflevector <$N x $LT> %x, <$N x $LT> poison, <$N x i32> zeroinitializer
        %w  = add <$N x $LT> %u, %xv
        ret <$N x $LT> %w
    """
    quote
        t = Base.llvmcall($ir, NTuple{N, VecElement{T}}, Tuple{T, T}, x, s)
        FixedVector{N,T}(unvec(t))
    end
end

pushfirst(v::FixedVector, xs...) = prepend(v, xs)

function prepend(v::FixedVector{N,T}, w::Union{AbstractVector,Tuple}) where {N,T}
    n = length(w)
    t = ntuple(Val(N)) do i
        @inbounds i <= n ? convert(T, w[i]) : v[i-n]
    end
    FixedVector{N,T}(t)
end

function popfirst(v::FixedVector{N,T}) where {N,T}
    if hasshuffle(v, Val(false))
        U = uinttype(T)
        u = FixedVector{N,U}(1:N)
        p = ifelse.(u .< U(N), u, -U(1))
        shuffle(v, p, Val(false)), v[1]
    else
        @inbounds popat(v, 1)
    end
end

@inline function insert(v::FixedVector{N,T}, i::Integer, x) where {N,T}
    @boundscheck checkbounds(v, i)
    v = Tuple(v)
    t = ntuple(Val(N)) do j
        if j < i
            v[j]
        elseif j == i
            convert(T, x)
        else
            v[j-1]
        end
    end
    FixedVector{N,T}(t)
end

@inline function duplicate(v::FixedVector{N,T}, i::Integer) where {N,T}
    @static if v"1.12" <= VERSION < v"1.13-" && Sys.ARCH == :x86_64
        # @inbounds leads to bug, similar to julia#59203
        checkbounds(v, i)
    else
        @boundscheck checkbounds(v, i)
    end
    t = if ishwtype(T)
        NN = smallint(N)
        ii = i % typeof(NN)
        c = circshift(v, 1)
        ntuple(Val(NN)) do j
            j <= ii ? v[j] : c[j]
        end
    else
        ntuple(Val(N)) do j
            j <= i ? v[j] : v[j-1]
        end
    end
    FixedVector{N,T}(t)
end

@propagate_inbounds deleteat(v::FixedVector, i::Integer) = first(popat(v, i))

@inline function popat(v::FixedVector{N,T}, i::Integer) where {N,T}
    @boundscheck checkbounds(v, i)
    if hasshuffle(v, Val(false))
        U = uinttype(T)
        ii = i % U
        w = first(@inline popfirst(v))
        t = ntuple(Val(N % U)) do j
            j < ii ? v[j] : w[j]
        end
    else
        t = ntuple(Val(N)) do j
            if j < i
                v[j]
            elseif j < N
                v[j+1]
            else
                default(T)
            end
        end
    end
    FixedVector{N,T}(t),
    @static if v"1.11" <= VERSION < v"1.13-" && Sys.ARCH == :x86_64
        # @inbounds leads to bug (with 1.12 not just for floats), see julia#59203
        v[i]
    else
        @inbounds v[i]
    end
end

"""
    circshift(v::AbstractFixedVector{N,T}, k::Integer) where {N,T} -> FixedVector{N,T}
    circshift(v::AbstractFixedVector{N,T}, ::Val{k}) where {N,T,k} -> FixedVector{N,T}

Rotate `v` by `k` positions towards higher indices and return the result.
A negative value of `k` corresponds to a rotation towards lower indices.

See also [`circshift!`](@ref circshift!(::MutableFixedVector, ::Union{Integer,Val})).

# Examples
```jldoctest
julia> v = FixedVector{4}(1:4);

julia> circshift(v, 1)
4-element FixedVector{4, $Int}:
 4
 1
 2
 3

julia> circshift(v, Val(-1))
4-element FixedVector{4, $Int}:
 2
 3
 4
 1
```
"""
circshift(::AbstractFixedVector, ::Union{Integer,Val})

"""
    unsafe_circshift(v::AbstractFixedVector{N,T}, k::Integer) where {N,T} -> FixedVector{N,T}
    unsafe_circshift!(v::MutableFixedVector, k::Integer) -> v

These are faster versions of `circshift` and `circshift!`. They assume `-N ≤ k < N`.
This avoids the comparatively costly integer division with remainder.

See also [`circshift`](@ref circshift(::AbstractFixedVector, ::Union{Integer,Val})),
[`circshift!`](@ref circshift!(::MutableFixedVector, ::Union{Integer,Val})).
"""
unsafe_circshift(::AbstractFixedVector, ::Integer),
unsafe_circshift!(::MutableFixedVector, ::Integer)

@inline function unsafe_circshift(v::AbstractFixedVector{N,T}, k::Integer) where {N,T}
    if N == 1
        FixedVector{N,T}(v)
    elseif ishwtype(T) && ispow2(N) && 8 <= bitsize(T)*N <= 64
        convert(FixedVector{N,T}, bitrotate(bits(v), bitsize(T)*k))
    elseif hasshuffle(v, Val(true))
        U = uinttype(T)
        u = FixedVector{N,U}(0:N-1)
        p = if ispow2(N)
            (u .- (k % U)) .& U(N-1)
        else
            l = k >= 0 ? k % U : (k+N) % U
            u1 = u .- l
            u2 = u1 .+ N % U
            ifelse.(u .< l, u2, u1)
        end
        shuffle(fixedvector(v), p, Val(true))
    else
        t = ntuple(Val(N % SmallLength)) do i
            k2 = ifelse(signbit(k), k+N, k) % SmallLength
            @inbounds if i <= k2
                v[(i-k2)+N]
            else
                v[i-k2]
            end
        end
        FixedVector{N,T}(t)
    end
end

@inline function circshift(v::AbstractFixedVector{N,T}, k::Integer) where {N,T}
    if N == 1
        FixedVector{N,T}(v)
    elseif ishwtype(T) && ispow2(N) && 8 <= bitsize(T)*N <= 128  # for Bool one could go up to 512 bits
        convert(FixedVector{N,T}, bitrotate(bits(v), bitsize(T)*k))
    else
        k1 = ispow2(N) ? k & (N-1) : unsafe_rem(k, UInt16(N))
        unsafe_circshift(v, k1)
    end
end

function circshift(v::AbstractFixedVector{N,T}, ::Val{K}) where {N,T,K}
    if v isa MutableFixedVector && ishwtype(T)
        circshift_hw(v, Val(K))
    else
        circshift(v, K)
    end
end

@inline @generated function circshift_hw(v::MutableFixedVector{N,T}, ::Val{K}) where {N,T,K}
    H = hwtype(T)
    M = llvm_type(H)
    b = sizeof(T)
    s = join(("i32 " * string(mod(i-K, N)) for i in 0:N-1), ", ")
    ir = VERSION > v"1.12-" ? """
        %a = load <$N x $M>, ptr %0, align $b
        %b = shufflevector <$N x $M> %a, <$N x $M> poison, <$N x i32> <$s>
        ret <$N x $M> %b
    """ : """
        %p = inttoptr i64 %0 to <$N x $M>*
        %a = load <$N x $M>, <$N x $M>* %p, align $b
        %b = shufflevector <$N x $M> %a, <$N x $M> poison, <$N x i32> <$s>
        ret <$N x $M> %b
    """
    quote
        b = Base.llvmcall($ir, NTuple{N,VecElement{$H}}, Tuple{Ptr{T}}, pointer(v))
        FixedVector{N,T}(unvec(T, b))
    end
end

"""
    circshift!(v::MutableFixedVector, k::Integer) -> v
    circshift!(v::MutableFixedVector, ::Val{k}) where k -> v

Rotate `v` in-place by `k` positions towards higher indices and return `v`.
A negative value of `k` corresponds to a rotation towards lower indices.

See also [`circshift`](@ref circshift(::AbstractFixedVector, ::Union{Integer,Val})).

# Examples
```jldoctest
julia> v = MutableFixedVector{4}(1:4);

julia> circshift!(v, 1)
4-element MutableFixedVector{4, $Int}:
 4
 1
 2
 3

julia> circshift!(v, Val(-1))  # undo previous step
4-element MutableFixedVector{4, $Int}:
 1
 2
 3
 4
```
"""
circshift!(::MutableFixedVector, ::Union{Integer,Val})

@inline function unsafe_circshift!(v::MutableFixedVector, k::Integer)
    v .= unsafe_circshift(v, k)
end

@inline function circshift!(v::MutableFixedVector, k::Integer)
    v .= circshift(v, k)
end

function circshift!(v::MutableFixedVector{N,T}, ::Val{K}) where {N,T,K}
    ishwtype(T) || return circshift!(v, K)
    vec_circshift!(pointer(v), Val(N), Val(K))
    v
end

@inline @generated function vec_circshift!(ptr::Ptr{T}, ::Val{N}, ::Val{K}) where {N,T,K}
    M = llvm_type(hwtype(T))
    b = sizeof(T)
    s = join(("i32 " * string(mod(i-K, N)) for i in 0:N-1), ", ")
    ir = VERSION > v"1.12-" ? """
        %a = load <$N x $M>, ptr %0, align $b
        %b = shufflevector <$N x $M> %a, <$N x $M> poison, <$N x i32> <$s>
        store <$N x $M> %b, ptr %0, align $b
        ret void
    """ : """
        %p = inttoptr i64 %0 to <$N x $M>*
        %a = load <$N x $M>, <$N x $M>* %p, align $b
        %b = shufflevector <$N x $M> %a, <$N x $M> poison, <$N x i32> <$s>
        store <$N x $M> %b, <$N x $M>* %p, align $b
        ret void
    """
    quote
        Base.llvmcall($ir, Cvoid, Tuple{Ptr{T}}, ptr)
    end
end

@inline @generated function reverse!(v::MutableFixedVector{N,T}) where {N,T}
    ishwtype(T) || return invoke(reverse!, Tuple{AbstractVector{T}})
    M = llvm_type(hwtype(T))
    b = sizeof(T)
    s = join(("i32 " * string(i) for i in N-1:-1:0), ", ")
    ir = VERSION > v"1.12-" ? """
        %a = load <$N x $M>, ptr %0, align $b
        %b = shufflevector <$N x $M> %a, <$N x $M> poison, <$N x i32> <$s>
        store <$N x $M> %b, ptr %0, align $b
        ret void
    """ : """
        %p = inttoptr i64 %0 to <$N x $M>*
        %a = load <$N x $M>, <$N x $M>* %p, align $b
        %b = shufflevector <$N x $M> %a, <$N x $M> poison, <$N x i32> <$s>
        store <$N x $M> %b, <$N x $M>* %p, align $b
        ret void
    """
    quote
        Base.llvmcall($ir, Cvoid, Tuple{Ptr{T}}, pointer(v))
        v
    end
end

# checked arithmetic

"""
    Base.Checked.checked_neg(v::AbstractFixedVector{N}) where N -> FixedVector{N}

Returns `-v`, but throws an `OverflowError` if an overflow occurs.
The elements of `v` must be integers.

See also [`Base.Checked.checked_sub`](@ref Base.Checked.checked_sub(::AbstractFixedVector, ::AbstractFixedVector)).
"""
Base.Checked.checked_neg(::AbstractFixedVector)

function Base.Checked.checked_neg(v::AbstractFixedVector{N,T}) where {N, T <: Integer}
    T <: Union{Bool, BigInt} && return -v
    u, s = Base.Checked.sub_with_overflow(zero(v), v)
    isempty(s) || overflowerror("overflow during negation")
    u
end

for op_with_name in ((:+, "add"), (:-, "sub"))
    global op, op_name = op_with_name
    global op_with_overflow = Symbol(op_name, "_with_overflow")
    global checked_op = Symbol("checked_", op_name)

    @eval begin
        example_ans = if op == :+
            (Int8[-128, 4, 4, -128], SmallBitSet{UInt8}([4]))
        else
            (Int8[-128, -2, 0, 126], SmallBitSet{UInt8}([1]))
        end

        """
            Base.Checked.$op_with_overflow(v::AbstractFixedVector{N,T}, w::AbstractFixedVector{N,T}) where {N,T} -> Tuple{FixedVector{N,T}, SmallBitSet}

        Returns `v$(op)w` as the first component of the resulting tuple.
        The second component is a `SmallBitSet` whose elements are the indices where an overflow occurred.
        The element type `T` must be a bit integer type.

        See also [`Base.Checked.$checked_op`](@ref Base.Checked.$checked_op(::AbstractFixedVector, ::AbstractFixedVector)).

        # Example
        ```jldoctest
        julia> v = FixedVector{4,Int8}((0, 1, 2, 127));

        julia> w = FixedVector{4,Int8}((-128, 3, 2, 1));

        julia> Base.Checked.$op_with_overflow(v, w)
        $example_ans
        ```
        """
        Base.Checked.$op_with_overflow(::AbstractFixedVector, ::AbstractFixedVector)

        @inline @generated function Base.Checked.$op_with_overflow(v::AbstractFixedVector{N,T}, w::AbstractFixedVector{N,T}) where {N, T <: AbstractBitInteger}
            L = llvm_type(T)
            VL = llvm_type(N, T)
            U = uinttype(Val(N))
            LU = llvm_type(U)
            bc = if bitsize(U) == N
                """
                    %d = bitcast <$N x i1> %c to $LU
                """
            else
                """
                    %d1 = bitcast <$N x i1> %c to i$N
                    %d = zext i$N %d1 to $LU
                """
            end
            s = T <: Signed ? 's' : 'u'
            ir = """
                declare {<$N x $L>, <$N x i1>} @llvm.$s$($op_name).with.overflow.$VL(<$N x $L>, <$N x $L>)
                define {<$N x $L>, $LU} @f(<$N x $L>, <$N x $L>) alwaysinline {
                    %a = call {<$N x $L>, <$N x i1>} @llvm.$s$($op_name).with.overflow.$VL(<$N x $L> %0, <$N x $L> %1)
                    %b = extractvalue {<$N x $L>, <$N x i1>} %a, 0
                    %c = extractvalue {<$N x $L>, <$N x i1>} %a, 1
                    $bc
                    %e = insertvalue {<$N x $L>, $LU} poison, <$N x $L> %b, 0
                    %f = insertvalue {<$N x $L>, $LU} %e, $LU %d, 1
                    ret {<$N x $L>, $LU} %f
                }
            """
            quote
                u, o = Base.llvmcall(($ir, "f"), Tuple{NTuple{N,VecElement{T}}, $U},
                    Tuple{NTuple{N,VecElement{T}}, NTuple{N,VecElement{T}}}, vec(v.t), vec(w.t))
                FixedVector(unvec(u)), _SmallBitSet(o)
            end
        end

    end
end

"""
    Base.Checked.checked_add(v::AbstractFixedVector{N}, ws::AbstractFixedVector{N}...) where N -> FixedVector{N}

Returns the sum of the argument vectors, but throws an `OverflowError` if an overflow occurs.
All arguments must have integer elements.

See also [`Base.Checked.add_with_overflow`](@ref Base.Checked.add_with_overflow(::AbstractFixedVector, ::AbstractFixedVector)).
"""
Base.Checked.checked_add(::AbstractFixedVector, ::AbstractFixedVector...)

Base.Checked.checked_add(v::AbstractFixedVector{N, <:Integer}) where N = +v

function Base.Checked.checked_add(v::AbstractFixedVector{N, <:Integer}, ws::Vararg{AbstractFixedVector{N, <:Integer}, M}) where {N,M}
    T = promote_type(eltype(v), map(eltype, ws)...)
    T <: Union{Bool, BigInt} && return +(v, ws...)
    U = uinttype(Val(N))
    u, s = foldl(ws; init = (FixedVector{N,T}(v), SmallBitSet{U}())) do (vv, s), ww
        uu, t = Base.Checked.add_with_overflow(vv, FixedVector{N,T}(ww))
        uu, union(s, t)
    end
    isempty(s) || overflowerror("overflow during addition")
    u
end

"""
    Base.Checked.checked_sub(v::AbstractFixedVector{N}, w::AbstractFixedVector{N}) where N -> FixedVector{N}

Returns `v-w`, but throws an `OverflowError` if an overflow occurs.
The elements of `v` and `w` must be integers.

See also [`Base.Checked.sub_with_overflow`](@ref Base.Checked.sub_with_overflow(::AbstractFixedVector, ::AbstractFixedVector)).
"""
Base.Checked.checked_sub(::AbstractFixedVector, ::AbstractFixedVector)

function Base.Checked.checked_sub(v::AbstractFixedVector{N,T1}, w::AbstractFixedVector{N,T2}) where {N, T1 <: Integer, T2 <: Integer}
    T = promote_type(T1, T2)
    T == Union{Bool, BigInt} && return v-w
    u, s = Base.Checked.sub_with_overflow(FixedVector{N,T}(v), FixedVector{N,T}(w))
    isempty(s) || overflowerror("overflow during subtraction")
    u
end

"""
    Base.Checked.mul_with_overflow(c::T, v::AbstractFixedVector{N,T}) where {N,T} -> Tuple{FixedVector{N,T}, SmallBitSet}
    Base.Checked.mul_with_overflow(v::AbstractFixedVector{N,T}, c::T) where {N,T} -> Tuple{FixedVector{N,T}, SmallBitSet}

Returns `c*v` as the first component of the resulting tuple.
The second component is a `SmallBitSet` whose elements are the indices where an overflow occurred.
The element type `T` must be a bit integer type.

See also [`Base.Checked.checked_mul`](@ref Base.Checked.checked_mul(::Integer, ::AbstractFixedVector)).

# Example
```jldoctest
julia> v = FixedVector{4,Int8}((0, 1, 2, 127));

julia> Base.Checked.mul_with_overflow(Int8(2), v)
(Int8[0, 2, 4, -2], SmallBitSet{UInt8}([4]))
```
"""
Base.Checked.mul_with_overflow(::Integer, ::AbstractFixedVector),
Base.Checked.mul_with_overflow(::AbstractFixedVector, ::Integer)

@inline @generated function Base.Checked.mul_with_overflow(c::T, v::AbstractFixedVector{N,T}) where {N, T <: AbstractBitInteger}
    L = llvm_type(T)
    VL = llvm_type(N, T)
    U = uinttype(Val(N))
    LU = llvm_type(U)
    bc = if bitsize(U) == N
        """
            %d = bitcast <$N x i1> %c to $LU
        """
    else
        """
            %d1 = bitcast <$N x i1> %c to i$N
            %d = zext i$N %d1 to $LU
        """
    end
    s = T <: Signed ? 's' : 'u'
    ir = """
        declare {<$N x $L>, <$N x i1>} @llvm.$(s)mul.with.overflow.$VL(<$N x $L>, <$N x $L>)
        define {<$N x $L>, $LU} @f(<$N x $L>, <$N x $L>) alwaysinline {
            %a = call {<$N x $L>, <$N x i1>} @llvm.$(s)mul.with.overflow.$VL(<$N x $L> %0, <$N x $L> %1)
            %b = extractvalue {<$N x $L>, <$N x i1>} %a, 0
            %c = extractvalue {<$N x $L>, <$N x i1>} %a, 1
            $bc
            %e = insertvalue {<$N x $L>, $LU} poison, <$N x $L> %b, 0
            %f = insertvalue {<$N x $L>, $LU} %e, $LU %d, 1
            ret {<$N x $L>, $LU} %f
        }
    """
    quote
        w = FixedVector(ntuple(Returns(c), Val(N)))
        u, o = Base.llvmcall(($ir, "f"), Tuple{NTuple{N,VecElement{T}}, $U},
            Tuple{NTuple{N,VecElement{T}}, NTuple{N,VecElement{T}}}, vec(v.t), vec(w.t))
        FixedVector(unvec(u)), _SmallBitSet(o)
    end
end

Base.Checked.mul_with_overflow(v::AbstractFixedVector{N,T}, c::T) where {N, T <: AbstractBitInteger} = Base.Checked.mul_with_overflow(c, v)

"""
    Base.Checked.checked_mul(c, v::AbstractFixedVector{N}) where N -> FixedVector{N}
    Base.Checked.checked_mul(v::AbstractFixedVector{N}, c) where N -> FixedVector{N}

Returns `c*v`, but throws an `OverflowError` if an overflow occurs.
The scalar `c` and the elements of `v` must be integers.

See also [`Base.Checked.mul_with_overflow`](@ref Base.Checked.mul_with_overflow(::Integer, ::AbstractFixedVector)).
"""
Base.Checked.checked_mul(::Integer, ::AbstractFixedVector),
Base.Checked.checked_mul(::AbstractFixedVector, ::Integer)

function Base.Checked.checked_mul(c::T1, v::AbstractFixedVector{N,T2}) where {N, T1 <: Integer, T2 <: Integer}
    T = promote_type(T1, T2)
    T <: Union{Bool, BigInt} && return c*v
    u, s = Base.Checked.mul_with_overflow(T(c), FixedVector{N,T}(v))
    isempty(s) || overflowerror("overflow during multiplication")
    u
end

Base.Checked.checked_mul(v::AbstractFixedVector{N,T1}, c::T2) where {N, T1 <: Integer, T2 <: Integer} = Base.Checked.checked_mul(c, v)

"""
    $(@__MODULE__).default(::Type{T}) where T -> T
    $(@__MODULE__).default(::T) where T -> T

Return the default value of type `T` used for filling unused elements of an `AbstractSmallVector`.
This must be defined as `zero(T)` if `T` supports algebraic operations. Otherwise it can
be any value of type `T`.

This function has methods for number types, bits types, `Symbol`, `AbstractChar`, `AbstractString`,
`Tuple`, `NamedTuple`, `AbstractFixedVector`, `AbstractSmallVector` und `SmallBitSet`.
Methods for other types must be defined explicitly, see the examples below.

See also `Base.isbitstype`.

# Examples

We start by defining a default value for an immutable struct.
```jldoctest default
julia> import $(@__MODULE__): default

julia> struct A x::Int end

julia> default(::Type{A}) = A(0);
```
For a mutable struct one needs to create an object first.
```jldoctest default
julia> mutable struct B x::Int end

julia> const b0 = B(0);

julia> default(::Type{B}) = b0;
```
For mutable parametric types one can use a generated function.
```jldoctest default
julia> mutable struct C{T} x::T end

julia> @generated default(::Type{C{T}}) where T = C(default(T));

julia> default(C{Bool})
C{Bool}(false)

julia> default(C{Bool}) === default(C{Bool})  # do we always get the same object?
true
```
"""
default(::T) where T = default(T)

function default(::Type{T}) where T
    if isbitstype(T)
        default_bitstype(T)
    elseif Int <: T
        0
    else
        error(LazyString("no default value defined for type ", T))
    end
end

@assume_effects :total function default_bitstype(::Type{T}) where T
    m8, m1 = divrem(Base.packedsize(T), 8)
    t8 = ntuple(Returns(UInt64(0)), Val(m8))
    t1 = ntuple(Returns(UInt8(0)), Val(m1))
    reinterpret(T, (t8, t1))
end

default(::Type{T}) where T <: Number = zero(T)
default(::Type{T}) where T <: AbstractChar = T(0)
default(::Type{<:AbstractString}) = ""
default(::Type{Symbol}) = Symbol()

default(::Type{T}) where T <: Tuple = ntuple(i -> default(fieldtype(T, i)), Val(fieldcount(T)))
default(::Type{NamedTuple{K,T}}) where {K,T} = NamedTuple{K}(default(T))
default(::Type{Pair{K,V}}) where {K,V} = default(K) => default(V)

default(::Type{V}) where {N,T,V<:AbstractFixedVector{N,T}} = V(default(NTuple{N,T}))

#
# bit conversions
#

function hwtype(::Type{T}) where T
# the resulting type must have the same size as T
    !ismutabletype(T) && isstructtype(T) && fieldcount(T) == 1 ? hwtype(fieldtype(T, 1)) : T
end

ishwtype(::Type{T}) where T = hwtype(T) <: HWType

hwvalue(x::HWType) = x

function hwvalue(x::T) where T
    @assert !ismutabletype(T) && isstructtype(T) && fieldcount(T) == 1 "default method does not apply to type $T"
    hwvalue(getfield(x, 1))
end

from_hwvalue(::Type{T}, x::T) where T <: HWType = x

@generated function from_hwvalue(::Type{T}, x) where T
    @assert !ismutabletype(T) && isstructtype(T) && fieldcount(T) == 1 "default method does not apply to type $T"
    Expr(:new, T, Expr(:call, :from_hwvalue, fieldtype(T, 1), :x))
end

@inline vec(t::Union{NTuple{N}, AbstractFixedVector{N}}) where N = ntuple(i -> VecElement(hwvalue(t[i])), Val(N))

@inline unvec(t::NTuple{N,VecElement}) where N = ntuple(i -> t[i].value, Val(N))
@inline unvec(::Type{T}, t::NTuple{N,VecElement}) where {T,N} = ntuple(i -> from_hwvalue(T, t[i].value), Val(N))

"""
    bits(v::AbstractFixedVector{N,T}) where {N,T} -> Unsigned

Convert the given vector to an unsigned integer. The element type `T` must ultimately be a subtype of `$HWType`.
Here "ultimately" means that it can also be a `struct` with a single field of such a type or a nested sequence
of such structs. For example, `SmallBitSet{UInt8}` or `FixedVector{16,SmallBitSet{UInt8}}` are allowed.

For bit integers, hardware floats, `Char` and `Enum` types this is the same as `reinterpret(U, Tuple(v))`
on little-endian host machines, provided that `U` is an unsigned integer type with `N*bitsize(T)` bits,
possibly defined by the package `BitIntegers`. Otherwise the result will be zero-extended to the next
unsigned integer type `U` whose bit length is a power of `2`. The resulting integer is independent of
the endianness of the host machine.

If the element type is `Bool`, then each element only takes one bit in the return value.
If `N` is less than `8` or not a power of `2`, then the result will again be zero-extended.

The inverse operation can be done with `convert`.

See also
[`Base.convert`](@ref convert(::Type{<:AbstractFixedVector}, ::Unsigned)),
[`$(@__MODULE__).bitsize`](@ref), `Base.HWReal`, `Base.reinterpret`, `BitIntegers`.

# Examples
```jldoctest
julia> FixedVector{4,Int8}(1:4) |> bits
0x04030201

julia> FixedVector{3}('a':'c') |> bits
0x00000000630000006200000061000000

julia> m = FixedVector{6,SmallBitSet{UInt32}}(SmallBitSet(1:k) for k in 1:6) |> bits
0x00000000000000000000003f0000001f0000000f000000070000000300000001

julia> typeof(m)
BitIntegers.UInt256

julia> FixedVector{22}(map(isodd, 1:22)) |> bits
0x00155555
```
"""
function bits(v::AbstractFixedVector{N,T}) where {N,T}
    if ishwtype(T)
        bits(map(hwvalue, v))
    else
        error(LazyString("element type ", T, " not supported"))
    end
end

@inline @generated function bits(v::AbstractFixedVector{N,T}) where {N, T <: HWType}
    M = nextpow(2, N)
    b = M*bitsize(T)
    U = uinttype(Val(b))
    L = llvm_type(T)
    ir = """
        %b = bitcast <$M x $L> %0 to i$b
        ret i$b %b
    """
    quote
        w = fixedvector(v, Val($M))
        t = Tuple(map(htol, w))
        m = Base.llvmcall($ir, $U, Tuple{NTuple{$M, VecElement{T}}}, vec(t))
        ltoh(m)
    end
end

#=
@generated function bits(v::AbstractFixedVector{N,T}) where {N, T <: Union{Int128,UInt128}}
    n = nextpow(2, N)
    U = Symbol(:UInt, n*128)
    z = ntuple(Returns(zero(T)), Val(n-N))
    quote
        t = (Tuple(v)..., $z...)
        reinterpret($U, t)
    end
end
=#

@inline @generated function bits(v::AbstractFixedVector{N,Bool}) where N
    U = uinttype(Val(N))
    M = bitsize(U)
    ir = """
        %b = trunc <$M x i8> %0 to <$M x i1>
        %c = bitcast <$M x i1> %b to i$M
        ret i$M %c
    """
    quote
        w = fixedvector(v, Val($M))
        m = Base.llvmcall($ir, $U, Tuple{NTuple{$M, VecElement{Bool}}}, vec(w))
        ltoh(m)
    end
end

"""
    convert(::Type{V}, x::Unsigned) where {N, T, V <: AbstractFixedVector{N,T}} -> V

Convert the unsigned integer `x` to a `FixedVector{N,T}` or `MutableFixedVector{N,T}`.
The bits of `x` are split into groups of size `bitsize(T)` and reinterpreted as elements of type `T`.
Unused bits are ignored and missing bits are taken as `0`. This is the inverse operation to `bits`,
and the restriction on the element type `T` stated there apply here as well.

See also [`bits`](@ref bits(::AbstractFixedVector)), [`$(@__MODULE__).bitsize`](@ref).

# Examples
```jldoctest
julia> v = convert(FixedVector{4,Int8}, 0x030201)
4-element FixedVector{4, Int8}:
 1
 2
 3
 0

julia> bits(v)
0x00030201

julia> convert(FixedVector{6,Bool}, 0xf0)
6-element FixedVector{6, Bool}:
 0
 0
 0
 0
 1
 1

julia> x = FixedVector{2,Char}('a':'b') |> bits
0x6200000061000000

julia> convert(FixedVector{2,Char}, x)
2-element FixedVector{2, Char}:
 'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
 'b': ASCII/Unicode U+0062 (category Ll: Letter, lowercase)

julia> using BitIntegers

julia> convert(FixedVector{4,SmallBitSet{UInt64}}, uint256"0x300000000000000020000000000000001")
4-element FixedVector{4, SmallBitSet{UInt64}}:
 SmallBitSet([1])
 SmallBitSet([2])
 SmallBitSet([1, 2])
 SmallBitSet([])
```
"""
convert(::Type{<:AbstractFixedVector}, ::Unsigned)

function convert(::Type{V}, x::Unsigned) where {N, T, V <: AbstractFixedVector{N,T}}
    H = hwtype(T)
    H <: HWType || error(LazyString("element type ", T, " not supported"))
    V(map(Fix1(from_hwvalue, T), convert(FixedVector{N,H}, x)))
end

@generated function convert(::Type{V}, x::U) where {N, T <: HWType, V <: AbstractFixedVector{N,T}, U <: Unsigned}
    M = llvm_type(T)
    b = N*bitsize(T)
    c = bitsize(U)
    if b == c
        ir = """
            %b = bitcast i$b %0 to <$N x $M>
            ret <$N x $M> %b
        """
    elseif b > c
        ir = """
            %b = zext i$c %0 to i$b
            %a = bitcast i$b %b to <$N x $M>
            ret <$N x $M> %a
        """
    else
        ir = """
            %b = trunc i$c %0 to i$b
            %a = bitcast i$b %b to <$N x $M>
            ret <$N x $M> %a
        """
    end
    quote
        $(Expr(:meta, :inline))
        v = Base.llvmcall($ir, NTuple{N, VecElement{T}}, Tuple{$U}, x)
        V(unvec(v))
    end
end

@generated function convert(::Type{V}, x::U) where {N, V <: AbstractFixedVector{N,Bool}, U <: Unsigned}
    c = bitsize(U)
    N2 = nextpow(2, N)   # work around an LLVM bug
    if N2 == c
        ir = """
            %b = bitcast i$N2 %0 to <$N2 x i1>
            %c = zext <$N2 x i1> %b to <$N2 x i8>
            ret <$N2 x i8> %c
        """
    elseif N2 > c
        ir = """
            %a = zext i$c %0 to i$N2
            %b = bitcast i$N2 %a to <$N2 x i1>
            %c = zext <$N2 x i1> %b to <$N2 x i8>
            ret <$N2 x i8> %c
        """
    else
        ir = """
            %a = trunc i$c %0 to i$N2
            %b = bitcast i$N2 %a to <$N2 x i1>
            %c = zext <$N2 x i1> %b to <$N2 x i8>
            ret <$N2 x i8> %c
        """
    end
    quote
        $(Expr(:meta, :inline))
        v2 = Base.llvmcall($ir, NTuple{$N2, VecElement{Bool}}, Tuple{$U}, x)
        v = ntuple(Base.Fix1(getindex, v2), Val(N))
        V(unvec(v))
    end
end

@generated function compress(v::AbstractFixedVector{N,T}, s::SmallBitSet{U}) where {N,T,U}
    @assert ishwtype(T)
    H = hwtype(T)
    L = llvm_type(H)
    NL = llvm_type(N, H)
    M = bitsize(U)
    resize2 = if M == N
        "select i1 1, i$N %2, i$N 0"
    elseif M > N
        "trunc i$M %2 to i$N"
    else # M < N
        "zext i$M %2 to i$N"
    end
    ir = if VERSION > v"1.12-"
        """
        declare void @llvm.masked.compressstore.$NL (<$N x $L>, ptr, <$N x i1>)
        define void @compress(ptr, <$N x $L>, i$M) alwaysinline {
            %a = $resize2
            %b = bitcast i$N %a to <$N x i1>
            call void @llvm.masked.compressstore.$NL(<$N x $L> %1, ptr %0, <$N x i1> %b)
            ret void
        }
        """
    else
        """
        declare void @llvm.masked.compressstore.$NL (<$N x $L>, ptr, <$N x i1>)
        define void @compress(i64, <$N x $L>, i$M) alwaysinline {
            %a = $resize2
            %b = bitcast i$N %a to <$N x i1>
            %p = inttoptr i64 %0 to <$N x $L>*
            call void @llvm.masked.compressstore.$NL(<$N x $L> %1, <$N x $L>* %p, <$N x i1> %b)
            ret void
        }
        """
    end
    quote
        @inline
        w = default(MutableFixedVector{N,T})
        Base.llvmcall(($ir, "compress"), Cvoid, Tuple{Ptr{T}, NTuple{N,VecElement{$H}}, U}, pointer(w), vec(v), bits(s))
        FixedVector(w)
    end
end
