text:
"""
I'm not sure on the details, but I think this function can only allocate up to

524288 doubles

that is it may not be able to allocate arrays with more than about half a million elements, or 4 megabytes.

Another limitation is that LLVM will assume that each `alloca` within a function can be merged.
Therefore,
ptr1 = alloca(100)
ptr2 = alloca(2_000)
ptr3 = alloca(1_000)
ptr4 = alloca(500)

will result in allocating 2000 doubles, and ptr1 == ptr2 == ptr3 == ptr4.
This is very likely not what someone writing the above intended, but I do not yet know a workaround.
"""
@generated function alloca(::Val{N}, ::Type{T} = Float64, ::Val{Align} = Val{64}()) where {N, T <: NativeTypes, Align}
    typ = llvmtype(T)
    ptyp = JuliaPointerType
    instrs = String[]
    push!(instrs, "%ptr = alloca $typ, i32 $N, align $Align")
    push!(instrs, "%iptr = ptrtoint $typ* %ptr to $ptyp")
    # push!(instrs, "%ptr = alloca i8, i32 $(N*sizeof(T)), align $Align")
    # push!(instrs, "%iptr = ptrtoint i8* %ptr to $ptyp")
    push!(instrs, "ret $ptyp %iptr")
    quote
        $(Expr(:meta,:inline))
        llvmcall(
            $(join(instrs, "\n")),
            Ptr{$T}, Tuple{}
        )
    end
end
@generated function alloca(N::Int32, ::Type{T} = Float64, ::Val{Align} = Val{64}()) where {T <: NativeTypes, Align}
    typ = llvmtype(T)
    ptyp = JuliaPointerType
    instrs = String[]
    push!(instrs, "%ptr = alloca $typ, i32 %0, align $Align")
    push!(instrs, "%iptr = ptrtoint $typ* %ptr to $ptyp")
    push!(instrs, "ret $ptyp %iptr")
    quote
        $(Expr(:meta,:inline))
        llvmcall(
            $(join(instrs, "\n")),
            Ptr{$T}, Tuple{Int32}, N
        )
    end
end
@inline function alloca(N::Integer, ::Type{T} = Float64, ::Val{Align} = Val{64}()) where {T, Align}
    alloca(N % Int32, T, Val{Align}())
end

"""
ReadOrWrite: 0 for read, 1 for write.
Function follows the LLVM definition
https://llvm.org/docs/LangRef.html#llvm-prefetch-intrinsic
such that
prefetch(ptr, Val(3), Val(0))
corresponds to prefetch0 on x86 (extreme locality)
"""
@generated function prefetch(ptr::Ptr{T}, ::Val{Locality}, ::Val{ReadOrWrite}) where {T, Locality, ReadOrWrite}
    prefetch_call_string = """%addr = inttoptr i$(8sizeof(Int)) %0 to i8*
    call void @llvm.prefetch(i8* %addr, i32 $ReadOrWrite, i32 $Locality, i32 1)
    ret void"""
    quote
        $(Expr(:meta, :inline))
        llvmcall(
            ("declare void @llvm.prefetch(i8*, i32, i32, i32)",
             $prefetch_call_string), Cvoid, Tuple{Ptr{$T}}, ptr
        )
    end
end
@inline function prefetch(ptr::Union{VectorizationBase.AbstractStridedPointer,Ptr}, i, ::Val{Locality}, ::Val{ReadOrWrite}) where {Locality, ReadOrWrite}
    prefetch(gep(ptr, i), Val{Locality}(), Val{ReadOrWrite}())
end
@inline prefetch(ptr::Ptr) = prefetch(ptr, Val{3}(), Val{0}())
@inline prefetch(ptr::Ptr, ::Val{L}) where {L} = prefetch(ptr, Val{L}(), Val{0}())
@inline prefetch(ptr::Ptr, i) = prefetch(ptr, i, Val{3}(), Val{0}())
@inline prefetch(ptr::Ptr, i, ::Val{L}) where {L} = prefetch(ptr, i, Val{L}(), Val{0}())

function valloc(::Type{T}, N::Int) where {T}
    @assert N > 0
    W, Wshift = VectorizationBase.pick_vector_width_shift(T)
    # We use padding to align the address of the first element, and
    # also to ensure that we can access past the last element up to
    # the next full vector width
    mem = Vector{T}(undef, (N + W) & (-W))
    addr = reinterpret(Int, pointer(mem))
    reg_size = VectorizationBase.REGISTER_SIZE
    off = (addr & (reg_size - 1)) >>> VectorizationBase.intlog2(sizeof(T))
    off = W - off
    view(mem, off + 1 : off + N)
end

# @generated function sub2ind(dims::NTuple{N}, I::NTuple{N}) where {N}
#     ex = :(I[$N] - 1)
#     for i = (N - 1):-1:1
#         ex = :(I[$i] - 1 + dims[$i] * $ex)
#     end
#     quote
#         $(Expr(:meta, :inline))
#         $ex + 1
#     end
# end
@generated function vload(
    ::Type{_Vec{_W,T}}, ptr::Ptr{T}, ::Val{Aligned}, ::Val{Nontemporal}
) where {_W,T <: NativeTypes,Aligned, Nontemporal}
    W = _W + 1
    @assert isa(Aligned, Bool)
    ptyp = JuliaPointerType
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    decl = VectorizationBase.LOAD_SCOPE_TBAA
    instrs = String[]
    if Aligned
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    flags = [""]
    align > 0 && push!(flags, "align $align")
    Nontemporal && push!(flags, "!nontemporal !{i32 1}")
    push!(flags, "!alias.scope !3")
    push!(flags, "!tbaa !5")
    push!(instrs, "%ptr = inttoptr $ptyp %0 to $vtyp*")
    push!(instrs, "%res = load $vtyp, $vtyp* %ptr" * join(flags, ", "))
    push!(instrs, "ret $vtyp %res")
    quote
        $(Expr(:meta, :inline))
        llvmcall(
            $((decl, join(instrs, "\n"))),
            Vec{$W,$T}, Tuple{Ptr{$T}}, ptr
        )
    end
end
@generated function vload(
    ptr::Ptr{T}, i::_MM{W,<:Integer}, ::Val{Aligned}, ::Val{Nontemporal}
) where {W,T <: NativeTypes,Aligned, Nontemporal}
    @assert isa(Aligned, Bool)
    ptyp = JuliaPointerType
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    decl = VectorizationBase.LOAD_SCOPE_TBAA
    instrs = String[]
    if Aligned
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    flags = [""]
    align > 0 && push!(flags, "align $align")
    Nontemporal && push!(flags, "!nontemporal !{i32 1}")
    push!(flags, "!alias.scope !3")
    push!(flags, "!tbaa !5")
    push!(instrs, "%typptr = inttoptr $ptyp %0 to i8*")
    push!(instrs, "%offsetptr = getelementptr inbounds i8, i8* %typptr, $ptyp %1")
    push!(instrs, "%ptr = bitcast i8* %offsetptr to $vtyp*")
    # push!(instrs, "%offsetptr = add nsw nuw $ptyp %0, %1") # uncommenting requires *8n
    # push!(instrs, "%ptr = inttoptr $ptyp %offsetptr to $vtyp*")
    push!(instrs, "%res = load $vtyp, $vtyp* %ptr" * join(flags, ", "))
    push!(instrs, "ret $vtyp %res")
    quote
        $(Expr(:meta, :inline))
        SVec(llvmcall(
            $((decl, join(instrs, "\n"))),
            Vec{$W,$T}, Tuple{Ptr{$T},Int}, ptr, (i.i % Int)
        ))
    end
end
@generated function vload(
    ptr::Ptr{T}, i::_MM{W,VectorizationBase.Static{N}}, ::Val{Aligned}, ::Val{Nontemporal}
) where {W,T <: NativeTypes,Aligned, Nontemporal,N}
    @assert isa(Aligned, Bool)
    ptyp = JuliaPointerType
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    decl = VectorizationBase.LOAD_SCOPE_TBAA
    instrs = String[]
    if Aligned
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    flags = [""]
    align > 0 && push!(flags, "align $align")
    Nontemporal && push!(flags, "!nontemporal !{i32 1}")
    push!(flags, "!alias.scope !3")
    push!(flags, "!tbaa !5")
    push!(instrs, "%typptr = inttoptr $ptyp %0 to i8*")
    push!(instrs, "%offsetptr = getelementptr inbounds i8, i8* %typptr, $ptyp $N")
    push!(instrs, "%ptr = bitcast i8* %offsetptr to $vtyp*")
    # push!(instrs, "%offsetptr = add nsw nuw $ptyp %0, %1") # uncommenting requires *8n
    # push!(instrs, "%ptr = inttoptr $ptyp %offsetptr to $vtyp*")
    push!(instrs, "%res = load $vtyp, $vtyp* %ptr" * join(flags, ", "))
    push!(instrs, "ret $vtyp %res")
    quote
        $(Expr(:meta, :inline))
        SVec(llvmcall(
            $((decl, join(instrs, "\n"))),
            Vec{$W,$T}, Tuple{Ptr{$T}}, ptr
        ))
    end
end
@generated function vload(
    ::Type{_Vec{_W,T}}, ptr::Ptr{T}, mask::U, ::Val{Aligned}
) where {_W,T <: NativeTypes,Aligned,U<:Unsigned}
    W = _W + 1
    @assert isa(Aligned, Bool)
    @assert 8sizeof(U) >= W
    ptyp = JuliaPointerType
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    mtyp_input = llvmtype(U)
    mtyp_trunc = "i$W"
    decls = String[ VectorizationBase.LOAD_SCOPE_TBAA ]
    instrs = String[]
    if Aligned
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    push!(instrs, "%ptr = inttoptr $ptyp %0 to $vtyp*")
    if mtyp_input == mtyp_trunc
        push!(instrs, "%mask = bitcast $mtyp_input %1 to <$W x i1>")
    else
        push!(instrs, "%masktrunc = trunc $mtyp_input %1 to $mtyp_trunc")
        push!(instrs, "%mask = bitcast $mtyp_trunc %masktrunc to <$W x i1>")
    end
    push!(decls,
        "declare $vtyp @llvm.masked.load.$(suffix(W,T))($vtyp*, i32, <$W x i1>, $vtyp)"
    )
    push!(instrs,
        "%res = call $vtyp @llvm.masked.load.$(suffix(W,T))($vtyp* %ptr, i32 $align, <$W x i1> %mask, $vtyp zeroinitializer), !alias.scope !3, !tbaa !5"#undef)"#
    )
    push!(instrs, "ret $vtyp %res")
    quote
        $(Expr(:meta, :inline))
        llvmcall(
            $((join(decls, "\n"), join(instrs, "\n"))),
            Vec{$W,$T}, Tuple{Ptr{$T}, $U}, ptr, mask
        )
    end
end
@generated function vload(
    ptr::Ptr{T}, i::_MM{W,<:Integer}, mask::U, ::Val{Aligned}
) where {W,T <: NativeTypes,U<:Unsigned,Aligned}
    ptyp = JuliaPointerType
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    mtyp_input = llvmtype(U)
    mtyp_trunc = "i$W"
    decls = String[ VectorizationBase.LOAD_SCOPE_TBAA ]
    instrs = String[]
    if Aligned
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    push!(instrs, "%typptr = inttoptr $ptyp %0 to i8*")
    push!(instrs, "%offsetptr = getelementptr inbounds i8, i8* %typptr, $ptyp %1")
    push!(instrs, "%ptr = bitcast i8* %offsetptr to $vtyp*")
    if mtyp_input == mtyp_trunc
        push!(instrs, "%mask = bitcast $mtyp_input %2 to <$W x i1>")
    else
        push!(instrs, "%masktrunc = trunc $mtyp_input %2 to $mtyp_trunc")
        push!(instrs, "%mask = bitcast $mtyp_trunc %masktrunc to <$W x i1>")
    end
    push!(decls, "declare $vtyp @llvm.masked.load.$(suffix(W,T))($vtyp*, i32, <$W x i1>, $vtyp)")
    push!(instrs,"%res = call $vtyp @llvm.masked.load.$(suffix(W,T))($vtyp* %ptr, i32 $align, <$W x i1> %mask, $vtyp zeroinitializer), !alias.scope !3, !tbaa !5")
    push!(instrs, "ret $vtyp %res")
    quote
        $(Expr(:meta, :inline))
        SVec(llvmcall(
            $((join(decls, "\n"), join(instrs, "\n"))),
            Vec{$W,$T}, Tuple{Ptr{$T}, Int, $U}, ptr, i.i % Int, mask
        ))
    end
end
@generated function vload(
    ptr::Ptr{T}, i::_MM{W,VectorizationBase.Static{N}}, mask::U, ::Val{Aligned}
) where {W,T <: NativeTypes,U<:Unsigned,Aligned,N}
    ptyp = JuliaPointerType
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    mtyp_input = llvmtype(U)
    mtyp_trunc = "i$W"
    decls = String[ VectorizationBase.LOAD_SCOPE_TBAA ]
    instrs = String[]
    if Aligned
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    push!(instrs, "%typptr = inttoptr $ptyp %0 to i8*")
    push!(instrs, "%offsetptr = getelementptr inbounds i8, i8* %typptr, $ptyp $N")
    push!(instrs, "%ptr = bitcast i8* %offsetptr to $vtyp*")
    if mtyp_input == mtyp_trunc
        push!(instrs, "%mask = bitcast $mtyp_input %1 to <$W x i1>")
    else
        push!(instrs, "%masktrunc = trunc $mtyp_input %1 to $mtyp_trunc")
        push!(instrs, "%mask = bitcast $mtyp_trunc %masktrunc to <$W x i1>")
    end
    push!(decls, "declare $vtyp @llvm.masked.load.$(suffix(W,T))($vtyp*, i32, <$W x i1>, $vtyp)")
    push!(instrs,"%res = call $vtyp @llvm.masked.load.$(suffix(W,T))($vtyp* %ptr, i32 $align, <$W x i1> %mask, $vtyp zeroinitializer), !alias.scope !3, !tbaa !5")
    push!(instrs, "ret $vtyp %res")
    quote
        $(Expr(:meta, :inline))
        SVec(llvmcall(
            $((join(decls, "\n"), join(instrs, "\n"))),
            Vec{$W,$T}, Tuple{Ptr{$T}, $U}, ptr, mask
        ))
    end
end

for v ∈ (:Vec, :SVec, :Val, :_MM)
    pargs = if v === :Val
        Union{Symbol,Expr}[ :(::Val{W}), :(ptr::Ptr{T})]
    elseif v === :_MM
        Union{Symbol,Expr}[:(ptr::Ptr{T})]
    else
        Union{Symbol,Expr}[:(::Type{$v{W,T}}), :(ptr::Ptr{T})]
    end
    for index ∈ (true,false)
        if v === :_MM
            index || continue
            icall = Union{Symbol,Expr}[:ptr, :i]#Expr(:call, :gep, :ptr, :i)]
            iargs = push!(copy(pargs), :(i::Union{_MM{W},AbstractSIMDVector{W,<:Integer}}))
        else
            if index
                icall = Union{Symbol,Expr}[:ptr, :(_MM{W}(i))]#Expr(:call, :gep, :ptr, :i)]
                iargs = push!(copy(pargs), :i)
            else
                icall = Union{Symbol,Expr}[:(Vec{W,T}), :ptr]
                iargs = pargs
            end
        end
        for mask ∈ [ :nothing, :Unsigned, :Mask ]
            if mask !== :nothing
                margs = push!(copy(iargs), Expr(:(::), :mask, mask))
                if mask === :Mask
                    mcall = push!(copy(icall), Expr(:call, :extract_data, :mask))
                else
                    mcall = push!(copy(icall), :mask)
                end
                fopts = [:vload,:vloada]
            else
                margs = iargs
                mcall = icall
                fopts = [:vload,:vloada,:vloadnt]
            end
            for f ∈ fopts
                body = Expr(:call, :vload, mcall...)
                push!(body.args, Expr(:call, Expr(:curly, :Val, f !== :vload))) # aligned arg
                if mask === :nothing
                    push!(body.args, Expr(:call, Expr(:curly, :Val, f === :vloadnt))) # nontemporal argf
                end
                if index
                    if v === :Vec
                        body = Expr(:call, :extract_data, body)
                    end
                else
                    if v !== :Vec
                        body = Expr(:call, :SVec, body)
                    end
                end
                @eval @inline function $f($(margs...)) where {W,T}
                    $body
                end
            end
        end
    end
end

@generated function vstore!(
    ptr::Ptr{T}, v::Vec{W,T}, ::Val{Aligned}, ::Val{Nontemporal}
) where {W,T <: NativeTypes,Aligned, Nontemporal}
    @assert isa(Aligned, Bool)
    ptyp = JuliaPointerType
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    instrs = String[]
    if Aligned# || Nontemporal
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    decl = VectorizationBase.STORE_TBAA
    flags = [""]
    align > 0 && push!(flags, "align $align")
    Nontemporal && push!(flags, "!nontemporal !{i32 1}")
    push!(flags, "!tbaa !7")
    push!(instrs, "%ptr = inttoptr $ptyp %0 to $vtyp*")
    push!(instrs, "store $vtyp %1, $vtyp* %ptr" * join(flags, ", "))
    push!(instrs, "ret void")
    quote
        $(Expr(:meta, :inline))
        llvmcall(
            $((decl,join(instrs, "\n"))),
            Cvoid, Tuple{Ptr{$T}, Vec{$W,$T}}, ptr, v
        )
    end
end
@generated function vstore!(
    ptr::Ptr{T}, v::Vec{W,T}, i::I, ::Val{Aligned}, ::Val{Nontemporal}
) where {W,T <: NativeTypes,I<:Integer,Aligned, Nontemporal}
    @assert isa(Aligned, Bool)
    ityp = llvmtype(I)
    ptyp = JuliaPointerType
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    instrs = String[]
    if Aligned# || Nontemporal
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    decl = VectorizationBase.STORE_TBAA
    flags = [""]
    align > 0 && push!(flags, "align $align")
    Nontemporal && push!(flags, "!nontemporal !{i32 1}")
    push!(flags, "!tbaa !7")
    push!(instrs, "%typptr = inttoptr $ptyp %0 to i8*")
    push!(instrs, "%offsetptr = getelementptr inbounds i8, i8* %typptr, $ityp %2")
    push!(instrs, "%ptr = bitcast i8* %offsetptr to $vtyp*")
    push!(instrs, "store $vtyp %1, $vtyp* %ptr" * join(flags, ", "))
    push!(instrs, "ret void")
    quote
        $(Expr(:meta, :inline))
        llvmcall(
            $((decl,join(instrs, "\n"))),
            Cvoid, Tuple{Ptr{$T}, Vec{$W,$T}, $I}, ptr, v, i
        )
    end
end

@generated function vstore!(
    ptr::Ptr{T}, v::Vec{W,T}, mask::U, ::Val{Aligned}# = Val{false}()
) where {W,T <: NativeTypes,Aligned,U<:Unsigned}
    @assert isa(Aligned, Bool)
    ptyp = JuliaPointerType
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    mtyp_input = llvmtype(U)
    mtyp_trunc = "i$W"
    instrs = String[]
    if Aligned
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    decl = VectorizationBase.STORE_TBAA
    push!(instrs, "%ptr = inttoptr $ptyp %0 to $vtyp*")
    if mtyp_input == mtyp_trunc
        push!(instrs, "%mask = bitcast $mtyp_input %2 to <$W x i1>")
    else
        push!(instrs, "%masktrunc = trunc $mtyp_input %2 to $mtyp_trunc")
        push!(instrs, "%mask = bitcast $mtyp_trunc %masktrunc to <$W x i1>")
    end
    decl = VectorizationBase.STORE_TBAA * "declare void @llvm.masked.store.$(suffix(W,T))($vtyp, $vtyp*, i32, <$W x i1>)"
    push!(instrs,
        "call void @llvm.masked.store.$(suffix(W,T))($vtyp %1, $vtyp* %ptr, i32 $align, <$W x i1> %mask), !tbaa !7"
    )
    push!(instrs, "ret void")
    quote
        $(Expr(:meta, :inline))
        llvmcall(
            $((decl, join(instrs, "\n"))),
            Cvoid, Tuple{Ptr{$T}, Vec{$W,$T}, $U},
            ptr, v, mask
        )
    end
end
@generated function vstore!(
    ptr::Ptr{T}, v::Vec{W,T}, i::I, mask::U, ::Val{Aligned}# = Val{false}()
) where {W,T <: NativeTypes,Aligned,U<:Unsigned,I<:Integer}
    @assert isa(Aligned, Bool)
    ityp = llvmtype(I)
    ptyp = JuliaPointerType
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    mtyp_input = llvmtype(U)
    mtyp_trunc = "i$W"
    instrs = String[]
    if Aligned
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    push!(instrs, "%typptr = inttoptr $ptyp %0 to i8*")
    push!(instrs, "%offsetptr = getelementptr inbounds i8, i8* %typptr, $ityp %2")
    push!(instrs, "%ptr = bitcast i8* %offsetptr to $vtyp*")
    if mtyp_input == mtyp_trunc
        push!(instrs, "%mask = bitcast $mtyp_input %3 to <$W x i1>")
    else
        push!(instrs, "%masktrunc = trunc $mtyp_input %3 to $mtyp_trunc")
        push!(instrs, "%mask = bitcast $mtyp_trunc %masktrunc to <$W x i1>")
    end
    decl = VectorizationBase.STORE_TBAA * "\ndeclare void @llvm.masked.store.$(suffix(W,T))($vtyp, $vtyp*, i32, <$W x i1>)"
    push!(instrs,
        "call void @llvm.masked.store.$(suffix(W,T))($vtyp %1, $vtyp* %ptr, i32 $align, <$W x i1> %mask), !tbaa !7"
    )
    push!(instrs, "ret void")
    quote
        $(Expr(:meta, :inline))
        llvmcall(
            $((decl, join(instrs, "\n"))),
            Cvoid, Tuple{Ptr{$T}, Vec{$W,$T}, $I, $U},
            ptr, v, i, mask
        )
    end
end

@generated function vnoaliasstore!(
    ptr::Ptr{T}, v::Vec{W,T}, i::I, ::Val{Aligned}, ::Val{Nontemporal}
) where {W,T <: NativeTypes, I <: Integer,Aligned, Nontemporal}
    @assert isa(Aligned, Bool)
    ityp = llvmtype(I)
    ptyp = JuliaPointerType
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    decls = String[ VectorizationBase.SCOPE_METADATA, VectorizationBase.STORE_TBAA ]
    instrs = String[]
    if Aligned# || Nontemporal
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    flags = [""]
    align > 0 && push!(flags, "align $align")
    Nontemporal && push!(flags, "!nontemporal !{i32 1}")
    push!(flags, "!noalias !3")
    push!(flags, "!tbaa !7")
    # decl = VectorizationBase.STORE_TBAA
    push!(instrs, "%typptr = inttoptr $ptyp %0 to i8*")
    push!(instrs, "%offsetptr = getelementptr inbounds i8, i8* %typptr, $ityp %2")
    push!(instrs, "%ptr = bitcast i8* %offsetptr to $vtyp*")
    push!(instrs, "store $vtyp %1, $vtyp* %ptr" * join(flags, ", "))
    push!(instrs, "ret void")
    quote
        $(Expr(:meta, :inline))
        llvmcall(
            $((join(decls, "\n"), join(instrs, "\n"))),
            Cvoid, Tuple{Ptr{$T}, Vec{$W,$T}, $I}, ptr, v, i
        )
    end
end

@generated function vnoaliasstore!(
    ptr::Ptr{T}, v::Vec{W,T}, i::I, mask::U, ::Val{Aligned}# = Val{false}()
) where {W,T <: NativeTypes,Aligned,U<:Unsigned,I<:Integer}
    @assert isa(Aligned, Bool)
    ityp = llvmtype(I)
    ptyp = JuliaPointerType
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    mtyp_input = llvmtype(U)
    mtyp_trunc = "i$W"
    decls = String[ VectorizationBase.SCOPE_METADATA, VectorizationBase.STORE_TBAA ]
    instrs = String[]
    if Aligned
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    # push!(flags, "!noalias !3")
    push!(instrs, "%typptr = inttoptr $ptyp %0 to i8*")
    push!(instrs, "%offsetptr = getelementptr inbounds i8, i8* %typptr, $ityp %2")
    push!(instrs, "%ptr = bitcast i8* %offsetptr to $vtyp*")
    if mtyp_input == mtyp_trunc
        push!(instrs, "%mask = bitcast $mtyp_input %3 to <$W x i1>")
    else
        push!(instrs, "%masktrunc = trunc $mtyp_input %3 to $mtyp_trunc")
        push!(instrs, "%mask = bitcast $mtyp_trunc %masktrunc to <$W x i1>")
    end
    push!(decls,
        "declare void @llvm.masked.store.$(suffix(W,T))($vtyp, $vtyp*, i32, <$W x i1>)"
    )
    push!(instrs,
        "call void @llvm.masked.store.$(suffix(W,T))($vtyp %1, $vtyp* %ptr, i32 $align, <$W x i1> %mask), !noalias !3, !tbaa !7"
    )
    push!(instrs, "ret void")
    quote
        $(Expr(:meta, :inline))
        llvmcall(
            $((join(decls, "\n"), join(instrs, "\n"))),
            Cvoid, Tuple{Ptr{$T}, Vec{$W,$T}, $I, $U},
            ptr, v, i, mask
        )
    end
end

let pargs = Union{Symbol,Expr}[:(ptr::Ptr{T}), :(v::AbstractSIMDVector{W,T})]
    for index ∈ 0:2
        if index == 0
            icall = Union{Symbol,Expr}[:ptr, :(extract_data(v))]
            iargs = pargs
        elseif index == 1
            icall = Union{Symbol,Expr}[:ptr, :(extract_data(v)), :(VectorizationBase.unwrap(i))]
            iargs = push!(copy(pargs), :(i::Integer))
        else#if index == 2
            icall = Union{Symbol,Expr}[:ptr, :(extract_data(v)), :(VectorizationBase.unwrap(i.i))]
            iargs = push!(copy(pargs), :(i::_MM{W}))
        # else
            # icall = Union{Symbol,Expr}[:ptr, :(extract_data(v)), Expr(:(.), :i, QuoteNode(:i))]
            # iargs = push!(copy(pargs), Expr(:(::), :i, Expr(:curly, :_MM, :W)))
        end
        for mask ∈ [ :nothing, :Unsigned, :Mask ]
            for prefix ∈ (:vstore, :vnoaliasstore)
                if mask !== :nothing
                    margs = push!(copy(iargs), Expr(:(::), :mask, mask))
                    if mask === :Mask
                        mcall = push!(copy(icall), Expr(:call, :extract_data, :mask))
                    else
                        mcall = push!(copy(icall), :mask)
                    end
                    suffix = [:!,:a!]
                else
                    margs = iargs
                    mcall = icall
                    suffix = [:!,:a!,:nt!]
                end
                for s ∈ suffix
                    f = Symbol(prefix, s)
                    body = Expr(:call, Symbol(prefix, :!))
                    append!(body.args, mcall)
                    push!(body.args, Expr(:call, Expr(:curly, :Val, s !== :!))) # aligned arg
                    if mask === :nothing
                        push!(body.args, Expr(:call, Expr(:curly, :Val, s === :nt!))) # nontemporal argf
                    end
                    @eval @inline function $f($(margs...)) where {W,T}
                        $body
                    end
                end
            end
        end
    end
end

# @generated function vloadscope(
#     ::Type{Vec{W,T}}, ptr::Ptr{T}, ::Val{Scope},
#     ::Val{Aligned}, ::Val{Nontemporal}
# ) where {W, T, Scope, Aligned, Nontemporal}
#     @assert isa(Aligned, Bool)
#     ptyp = JuliaPointerType
#     typ = llvmtype(T)
#     vtyp = "<$W x $typ>"
#     domain,scope,list = Scope::NTuple{3,Int}
#     decls = String["!$domain = !{!$domain}","!$scope = !{!$scope, !$domain}", "!$list = !{!$scope}"]
#     # decls = String[]
#     instrs = String[]
#     if Aligned
#         align = Base.datatype_alignment(Vec{W,T})
#     else
#         align = sizeof(T)   # This is overly optimistic
#     end
#     flags = [""]
#     align > 0 && push!(flags, "align $align")
#     push!(flags, "!alias.scope !$list")
#     Nontemporal && push!(flags, "!nontemporal !{i32 1}")
#     push!(instrs, "%ptr = inttoptr $ptyp %0 to $vtyp*")
#     push!(instrs, "%res = load $vtyp, $vtyp* %ptr" * join(flags, ", "))
#     push!(instrs, "ret $vtyp %res")
#     quote
#         $(Expr(:meta, :inline))
#         llvmcall($((join(decls, "\n"), join(instrs, "\n"))),
#             Vec{$W,$T}, Tuple{Ptr{$T}}, ptr)
#     end
# end

@generated function gather(
   ptr::_Vec{_W,Ptr{T}}, ::Val{Aligned}# = Val{false}()
) where {_W,T <: NativeTypes,Aligned}
    @assert isa(Aligned, Bool)
    W = _W + 1
    ptyp = JuliaPointerType
    vptyp = "<$W x $ptyp>"
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    vptrtyp = "<$W x $typ*>"
    instrs = String[]
    if Aligned
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    mask = join((", i1 true" for i ∈ 2:W))
    push!(instrs, "%ptr = inttoptr $vptyp %0 to $vptrtyp")
    decl = "declare $vtyp @llvm.masked.gather.$(suffix(W,T)).$(suffix(W,Ptr{T}))($vptrtyp, i32, <$W x i1>, $vtyp)"
    push!(instrs, "%res = call $vtyp @llvm.masked.gather.$(suffix(W,T)).$(suffix(W,Ptr{T}))($vptrtyp %ptr, i32 $align, <$W x i1> <i1 true$(mask)>, $vtyp undef)")
    push!(instrs, "ret $vtyp %res")
    quote
        $(Expr(:meta, :inline))
        llvmcall(
            $((decl, join(instrs, "\n"))),
            # Vec{$W,$T}, Tuple{Vec{$W,Ptr{$T}}}, ptr
            Vec{$W,$T}, Tuple{NTuple{$W,Core.VecElement{Ptr{$T}}}}, ptr
        )
    end
end

@generated function gather(
   ptr::_Vec{_W,Ptr{T}}, mask::U, ::Val{Aligned}# = Val{false}()
) where {_W,T <: NativeTypes,Aligned,U<:Unsigned}
    @assert isa(Aligned, Bool)
    W = _W + 1
    @assert 8sizeof(U) >= W
    ptyp = JuliaPointerType
    vptyp = "<$W x $ptyp>"
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    vptrtyp = "<$W x $typ*>"
    mtyp_input = llvmtype(U)
    mtyp_trunc = "i$W"
    instrs = String[]
    if Aligned
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    push!(instrs, "%ptr = inttoptr $vptyp %0 to $vptrtyp")
    if mtyp_input == mtyp_trunc
        push!(instrs, "%mask = bitcast $mtyp_input %1 to <$W x i1>")
    else
        push!(instrs, "%masktrunc = trunc $mtyp_input %1 to $mtyp_trunc")
        push!(instrs, "%mask = bitcast $mtyp_trunc %masktrunc to <$W x i1>")
    end
    decl = "declare $vtyp @llvm.masked.gather.$(suffix(W,T)).$(suffix(W,Ptr{T}))($vptrtyp, i32, <$W x i1>, $vtyp)"
    push!(instrs, "%res = call $vtyp @llvm.masked.gather.$(suffix(W,T)).$(suffix(W,Ptr{T}))($vptrtyp %ptr, i32 $align, <$W x i1> %mask, $vtyp zeroinitializer)")#undef)")
    push!(instrs, "ret $vtyp %res")
    quote
        $(Expr(:meta, :inline))
        llvmcall(
            $((decl, join(instrs, "\n"))),
            # Vec{$W,$T}, Tuple{Vec{$W,Ptr{$T}}, $U}, ptr, mask
            Vec{$W,$T}, Tuple{NTuple{$W,Core.VecElement{Ptr{$T}}}, $U}, ptr, mask
        )
    end
end
@generated function vload(
   ptr::Ptr{T}, i::_Vec{_W,I}, ::Val{Aligned}# = Val{false}()
) where {_W,T <: NativeTypes,I<:Integer,Aligned}
    @assert isa(Aligned, Bool)
    W = _W + 1
    ptyp = JuliaPointerType
    vptyp = "<$W x $ptyp>"
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    vptrtyp = "<$W x $typ*>"
    instrs = String[]
    if Aligned
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    ityp = llvmtype(I)
    vityp = "<$W x $ityp>"
    push!(instrs, "%sptr = inttoptr $ptyp %0 to i8*")
    push!(instrs, "%iptr = getelementptr inbounds i8, i8* %sptr, $vityp %1")
    push!(instrs, "%ptr = bitcast <$W x i8*> %iptr to $vptrtyp")
    # push!(instrs, "%sptr = inttoptr $ptyp %0 to i8*")
    # push!(instrs, "%ptr = getelementptr inbounds i8, i8* %sptr, $vityp %1")
    mask = join((", i1 true" for i ∈ 2:W))
    decl = "declare $vtyp @llvm.masked.gather.$(suffix(W,T)).$(suffix(W,Ptr{T}))($vptrtyp, i32, <$W x i1>, $vtyp)"
    push!(instrs, "%res = call $vtyp @llvm.masked.gather.$(suffix(W,T)).$(suffix(W,Ptr{T}))($vptrtyp %ptr, i32 $align, <$W x i1> <i1 true$(mask)>, $vtyp undef)")
    push!(instrs, "ret $vtyp %res")
    quote
        $(Expr(:meta, :inline))
        llvmcall(
            $((decl, join(instrs, "\n"))),
            Vec{$W,$T}, Tuple{Ptr{$T},Vec{$W,$I}}, ptr, i
        )
    end
end
@inline vload(ptr::Ptr, i::SVec{W,I}, ::Val{Aligned}) where {W,I<:Integer,Aligned} = SVec(vload(ptr, extract_data(i), Val{Aligned}()))
@inline vload(ptr::Ptr, i::SVec{W,I}, mask::Unsigned, ::Val{Aligned}) where {W,I<:Integer,Aligned} = SVec(vload(ptr, extract_data(i), mask, Val{Aligned}()))
@inline vload(ptr::Ptr, i::SVec{W,I}, mask::Mask{W}, ::Val{Aligned}) where {W,I<:Integer,Aligned} = SVec(vload(ptr, extract_data(i), mask.u, Val{Aligned}()))
@inline vload(ptr::Ptr, i::SVec{W,I}, ::Val{Aligned}, ::Val{false}) where {W,I<:Integer,Aligned} = SVec(vload(ptr, extract_data(i), Val{Aligned}()))
@inline vload(ptr::Ptr, i::SVec{W,I}, mask::Unsigned, ::Val{Aligned}, ::Val{false}) where {W,I<:Integer,Aligned} = SVec(vload(ptr, extract_data(i), mask, Val{Aligned}()))
@inline vload(ptr::Ptr, i::SVec{W,I}, mask::Mask{W}, ::Val{Aligned}, ::Val{false}) where {W,I<:Integer,Aligned} = SVec(vload(ptr, extract_data(i), mask.u, Val{Aligned}()))
@generated function vload(
   ptr::Ptr{T}, i::_Vec{_W,I}, mask::U, ::Val{Aligned}# = Val{false}()
) where {_W,T <: NativeTypes,I<:Integer,Aligned,U<:Unsigned}
    W = _W + 1
    @assert isa(Aligned, Bool)
    @assert 8sizeof(U) >= W
    ptyp = JuliaPointerType
    vptyp = "<$W x $ptyp>"
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    vptrtyp = "<$W x $typ*>"
    mtyp_input = llvmtype(U)
    mtyp_trunc = "i$W"
    instrs = String[]
    if Aligned
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    ityp = llvmtype(I)
    vityp = "<$W x $ityp>"
    push!(instrs, "%sptr = inttoptr $ptyp %0 to i8*")
    push!(instrs, "%iptr = getelementptr inbounds i8, i8* %sptr, $vityp %1")
    push!(instrs, "%ptr = bitcast <$W x i8*> %iptr to $vptrtyp")
    if mtyp_input == mtyp_trunc
        push!(instrs, "%mask = bitcast $mtyp_input %2 to <$W x i1>")
    else
        push!(instrs, "%masktrunc = trunc $mtyp_input %2 to $mtyp_trunc")
        push!(instrs, "%mask = bitcast $mtyp_trunc %masktrunc to <$W x i1>")
    end
    decl = "declare $vtyp @llvm.masked.gather.$(suffix(W,T)).$(suffix(W,Ptr{T}))($vptrtyp, i32, <$W x i1>, $vtyp)"
    push!(instrs, "%res = call $vtyp @llvm.masked.gather.$(suffix(W,T)).$(suffix(W,Ptr{T}))($vptrtyp %ptr, i32 $align, <$W x i1> %mask, $vtyp zeroinitializer)")#undef)")
    push!(instrs, "ret $vtyp %res")
    quote
        $(Expr(:meta, :inline))
        llvmcall(
            $((decl, join(instrs, "\n"))),
            Vec{$W,$T}, Tuple{Ptr{$T},Vec{$W,$I},$U}, ptr, i, mask
        )
    end
end
@generated function scatter!(
    ptr::_Vec{_W,Ptr{T}}, v::_Vec{_W,T}, ::Val{Aligned}# = Val{false}()
) where {_W,T <: NativeTypes,Aligned}
    @assert isa(Aligned, Bool)
    W = _W + 1
    ptyp = JuliaPointerType
    vptyp = "<$W x $ptyp>"
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    vptrtyp = "<$W x $typ*>"
    instrs = String[]
    if Aligned
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    push!(instrs, "%ptr = inttoptr $vptyp %1 to $vptrtyp")
    mask = join((", i1 true" for i ∈ 2:W))
    # push!(decls, "declare void @llvm.masked.scatter.$(suffix(W,T)).$(suffix(W,Ptr{T}))($vtyp, $vptrtyp, i32, <$W x i1>)")
    # push!(instrs, "call void @llvm.masked.scatter.$(suffix(W,T)).$(suffix(W,Ptr{T}))($vtyp %0, $vptrtyp %ptr, i32 $align, <$W x i1> <i1 true$(mask)>)")
    decl = "declare void @llvm.masked.scatter.$(suffix(W,T))($vtyp, $vptrtyp, i32, <$W x i1>)"
    push!(instrs, "call void @llvm.masked.scatter.$(suffix(W,T))($vtyp %0, $vptrtyp %ptr, i32 $align, <$W x i1> <i1 true$(mask)>)")
    push!(instrs, "ret void")
    quote
        $(Expr(:meta, :inline))
        llvmcall(
            $((decl, join(instrs, "\n"))),
            # Cvoid, Tuple{Vec{$W,$T}, Vec{$W,Ptr{$T}}}, v, ptr)
            Cvoid, Tuple{Vec{$W,$T}, NTuple{$W,Core.VecElement{Ptr{$T}}}}, v, ptr
        )
    end
end
@generated function scatter!(
    ptr::_Vec{_W,Ptr{T}}, v::_Vec{_W,T}, mask::U, ::Val{Aligned}# = Val{false}()
) where {_W,T <: NativeTypes,Aligned,U<:Unsigned}
    @assert isa(Aligned, Bool)
    W = _W + 1
    @assert 8sizeof(U) >= W
    ptyp = JuliaPointerType
    vptyp = "<$W x $ptyp>"
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    vptrtyp = "<$W x $typ*>"
    mtyp_input = llvmtype(U)
    mtyp_trunc = "i$W"
    instrs = String[]
    if Aligned
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    push!(instrs, "%ptr = inttoptr $vptyp %1 to $vptrtyp")
    if mtyp_input == mtyp_trunc
        push!(instrs, "%mask = bitcast $mtyp_input %2 to <$W x i1>")
    else
        push!(instrs, "%masktrunc = trunc $mtyp_input %2 to $mtyp_trunc")
        push!(instrs, "%mask = bitcast $mtyp_trunc %masktrunc to <$W x i1>")
    end
    # push!(decls,
        # "declare void @llvm.masked.scatter.$(suffix(W,T)).$(suffix(W,Ptr{T}))($vtyp, $vptrtyp, i32, <$W x i1>)")
    # push!(instrs,
        # "call void @llvm.masked.scatter.$(suffix(W,T)).$(suffix(W,Ptr{T}))($vtyp %0, $vptrtyp %ptr, i32 $align, <$W x i1> %mask)")
    decl = "declare void @llvm.masked.scatter.$(suffix(W,T))($vtyp, $vptrtyp, i32, <$W x i1>)"
    push!(instrs, "call void @llvm.masked.scatter.$(suffix(W,T))($vtyp %0, $vptrtyp %ptr, i32 $align, <$W x i1> %mask)")
    push!(instrs, "ret void")
    quote
        $(Expr(:meta, :inline))
        llvmcall(
            $((decl, join(instrs, "\n"))),
            Cvoid, Tuple{Vec{$W,$T}, NTuple{$W,Core.VecElement{Ptr{$T}}}, $U}, v, ptr, mask
        )
    end
end

@inline vload(ptr::_Vec{_W,Ptr{T}}) where {_W,T} = SVec(gather(ptr, Val{false}()))
@inline vload(ptr::_Vec{_W,Ptr{T}}, m::AbstractMask) where {_W,T} = SVec(gather(ptr, extract_data(m), Val{false}()))
@inline vstore!(ptr::_Vec{_W,Ptr{T}}, v) where {_W,T} = scatter!(ptr, extract_data(v), Val{false}())
@inline vstore!(ptr::_Vec{_W,Ptr{T}}, v, m::Union{AbstractMask,<:Unsigned}) where {_W,T} = scatter!(ptr, extract_data(v), extract_data(m), Val{false}())
@inline vnoaliasstore!(ptr::_Vec{_W,Ptr{T}}, v) where {_W,T} = scatter!(ptr, extract_data(v), Val{false}())
@inline vnoaliasstore!(ptr::_Vec{_W,Ptr{T}}, v, m::Union{AbstractMask,<:Unsigned}) where {_W,T} = scatter!(ptr, extract_data(v), extract_data(m), Val{false}())

@generated function vstore!(
    ptr::Ptr{T}, v::_Vec{_W,T}, i::_Vec{_W,I}, ::Val{Aligned}# = Val{false}()
) where {_W,T <: NativeTypes,Aligned, I<:Integer}
    @assert isa(Aligned, Bool)
    W = _W + 1
    ptyp = JuliaPointerType
    vptyp = "<$W x $ptyp>"
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    vptrtyp = "<$W x $typ*>"
    instrs = String[]
    if Aligned
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    ityp = llvmtype(I)
    vityp = "<$W x $ityp>"
    push!(instrs, "%sptr = inttoptr $ptyp %0 to i8*")
    push!(instrs, "%iptr = getelementptr inbounds i8, i8* %sptr, $vityp %2")
    push!(instrs, "%ptr = bitcast <$W x i8*> %iptr to $vptrtyp")
    # push!(instrs, "%sptr = inttoptr $ptyp %0 to $typ*")
    # push!(instrs, "%ptr = getelementptr inbounds $typ, $typ* %sptr, $vityp %2")
    mask = join((", i1 true" for i ∈ 2:W))
    # push!(decls, "declare void @llvm.masked.scatter.$(suffix(W,T)).$(suffix(W,Ptr{T}))($vtyp, $vptrtyp, i32, <$W x i1>)")
    # push!(instrs, "call void @llvm.masked.scatter.$(suffix(W,T)).$(suffix(W,Ptr{T}))($vtyp %0, $vptrtyp %ptr, i32 $align, <$W x i1> <i1 true$(mask)>)")
    decl = "declare void @llvm.masked.scatter.$(suffix(W,T))($vtyp, $vptrtyp, i32, <$W x i1>)"
    push!(instrs, "call void @llvm.masked.scatter.$(suffix(W,T))($vtyp %1, $vptrtyp %ptr, i32 $align, <$W x i1> <i1 true$(mask)>)")
    push!(instrs, "ret void")
    quote
        $(Expr(:meta, :inline))
        llvmcall(
            $((decl, join(instrs, "\n"))),
            Cvoid, Tuple{Ptr{$T}, Vec{$W,$T}, Vec{$W,$I}}, ptr, v, i
        )
    end
end
@generated function vstore!(
    ptr::Ptr{T}, v::_Vec{_W,T}, i::_Vec{_W,I}, mask::U, ::Val{Aligned}# = Val{false}()
) where {_W,T <: NativeTypes,Aligned,U<:Unsigned,I<:Integer}
    @assert isa(Aligned, Bool)
    W = _W + 1
    @assert 8sizeof(U) >= W
    ptyp = JuliaPointerType
    vptyp = "<$W x $ptyp>"
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    vptrtyp = "<$W x $typ*>"
    mtyp_input = llvmtype(U)
    mtyp_trunc = "i$W"
    instrs = String[]
    if Aligned
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    ityp = llvmtype(I)
    vityp = "<$W x $ityp>"
    push!(instrs, "%sptr = inttoptr $ptyp %0 to i8*")
    push!(instrs, "%iptr = getelementptr inbounds i8, i8* %sptr, $vityp %2")
    push!(instrs, "%ptr = bitcast <$W x i8*> %iptr to $vptrtyp")
    # push!(instrs, "%sptr = inttoptr $ptyp %0 to $typ*")
    # push!(instrs, "%ptr = getelementptr inbounds $typ, $typ* %sptr, $vityp %2")
    if mtyp_input == mtyp_trunc
        push!(instrs, "%mask = bitcast $mtyp_input %3 to <$W x i1>")
    else
        push!(instrs, "%masktrunc = trunc $mtyp_input %3 to $mtyp_trunc")
        push!(instrs, "%mask = bitcast $mtyp_trunc %masktrunc to <$W x i1>")
    end
    # push!(decls,
        # "declare void @llvm.masked.scatter.$(suffix(W,T)).$(suffix(W,Ptr{T}))($vtyp, $vptrtyp, i32, <$W x i1>)")
    # push!(instrs,
        # "call void @llvm.masked.scatter.$(suffix(W,T)).$(suffix(W,Ptr{T}))($vtyp %0, $vptrtyp %ptr, i32 $align, <$W x i1> %mask)")
    decl = "declare void @llvm.masked.scatter.$(suffix(W,T))($vtyp, $vptrtyp, i32, <$W x i1>)"
    push!(instrs, "call void @llvm.masked.scatter.$(suffix(W,T))($vtyp %1, $vptrtyp %ptr, i32 $align, <$W x i1> %mask)")
    push!(instrs, "ret void")
    quote
        $(Expr(:meta, :inline))
        llvmcall(
            $((decl, join(instrs, "\n"))),
            Cvoid, Tuple{Ptr{$T}, Vec{$W,$T}, Vec{$W,$I}, $U}, ptr, v, i, mask
        )
    end
end
@generated function vnoaliasstore!(
    ptr::Ptr{T}, v::_Vec{_W,T}, i::_Vec{_W,I}, ::Val{Aligned}# = Val{false}()
) where {_W,T <: NativeTypes, I<:Integer,Aligned}
    @assert isa(Aligned, Bool)
    W = _W + 1
    ptyp = JuliaPointerType
    vptyp = "<$W x $ptyp>"
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    vptrtyp = "<$W x $typ*>"
    instrs = String[]
    decls = String[ VectorizationBase.SCOPE_METADATA, VectorizationBase.STORE_TBAA, "declare void @llvm.masked.scatter.$(suffix(W,T))($vtyp, $vptrtyp, i32, <$W x i1>)" ]
    if Aligned
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    ityp = llvmtype(I)
    vityp = "<$W x $ityp>"
    push!(instrs, "%sptr = inttoptr $ptyp %0 to i8*")
    push!(instrs, "%iptr = getelementptr inbounds i8, i8* %sptr, $vityp %2")
    push!(instrs, "%ptr = bitcast <$W x i8*> %iptr to $vptrtyp")
    # push!(instrs, "%sptr = inttoptr $ptyp %0 to $typ*")
    # push!(instrs, "%ptr = getelementptr inbounds $typ, $typ* %sptr, $vityp %2")
    mask = join((", i1 true" for i ∈ 2:W))
    # push!(decls, "declare void @llvm.masked.scatter.$(suffix(W,T)).$(suffix(W,Ptr{T}))($vtyp, $vptrtyp, i32, <$W x i1>)")
    # push!(instrs, "call void @llvm.masked.scatter.$(suffix(W,T)).$(suffix(W,Ptr{T}))($vtyp %0, $vptrtyp %ptr, i32 $align, <$W x i1> <i1 true$(mask)>)")
    # decl = "declare void @llvm.masked.scatter.$(suffix(W,T))($vtyp, $vptrtyp, i32, <$W x i1>)"
    push!(instrs, "call void @llvm.masked.scatter.$(suffix(W,T))($vtyp %1, $vptrtyp %ptr, i32 $align, <$W x i1> <i1 true$(mask)>), !noalias !3, !tbaa !7")
    push!(instrs, "ret void")
    quote
        $(Expr(:meta, :inline))
        llvmcall(
            $((join(decls, "\n"), join(instrs, "\n"))),
            Cvoid, Tuple{Ptr{$T}, Vec{$W,$T}, Vec{$W,$I}}, ptr, v, i
        )
    end
end
@generated function vnoaliasstore!(
    ptr::Ptr{T}, v::_Vec{_W,T}, i::_Vec{_W,I}, mask::U, ::Val{Aligned}# = Val{false}()
) where {_W,T <: NativeTypes,U<:Unsigned,I<:Integer,Aligned}
    @assert isa(Aligned, Bool)
    W = _W + 1
    @assert 8sizeof(U) >= W
    ptyp = JuliaPointerType
    vptyp = "<$W x $ptyp>"
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    vptrtyp = "<$W x $typ*>"
    mtyp_input = llvmtype(U)
    mtyp_trunc = "i$W"
    instrs = String[]
    decls = String[ VectorizationBase.SCOPE_METADATA, VectorizationBase.STORE_TBAA, "declare void @llvm.masked.scatter.$(suffix(W,T))($vtyp, $vptrtyp, i32, <$W x i1>)" ]
    if Aligned
        align = Base.datatype_alignment(Vec{W,T})
    else
        align = sizeof(T)   # This is overly optimistic
    end
    ityp = llvmtype(I)
    vityp = "<$W x $ityp>"
    push!(instrs, "%sptr = inttoptr $ptyp %0 to i8*")
    push!(instrs, "%iptr = getelementptr inbounds i8, i8* %sptr, $vityp %2")
    push!(instrs, "%ptr = bitcast <$W x i8*> %iptr to $vptrtyp")
    # push!(instrs, "%sptr = inttoptr $ptyp %0 to $typ*")
    # push!(instrs, "%ptr = getelementptr inbounds $typ, $typ* %sptr, $vityp %2")
    if mtyp_input == mtyp_trunc
        push!(instrs, "%mask = bitcast $mtyp_input %3 to <$W x i1>")
    else
        push!(instrs, "%masktrunc = trunc $mtyp_input %3 to $mtyp_trunc")
        push!(instrs, "%mask = bitcast $mtyp_trunc %masktrunc to <$W x i1>")
    end
    # push!(decls,
        # "declare void @llvm.masked.scatter.$(suffix(W,T)).$(suffix(W,Ptr{T}))($vtyp, $vptrtyp, i32, <$W x i1>)")
    # push!(instrs,
        # "call void @llvm.masked.scatter.$(suffix(W,T)).$(suffix(W,Ptr{T}))($vtyp %0, $vptrtyp %ptr, i32 $align, <$W x i1> %mask)")
    push!(instrs, "call void @llvm.masked.scatter.$(suffix(W,T))($vtyp %1, $vptrtyp %ptr, i32 $align, <$W x i1> %mask), !noalias !3, !tbaa !7")
    push!(instrs, "ret void")
    quote
        $(Expr(:meta, :inline))
        llvmcall(
            $((join(decls, "\n"), join(instrs, "\n"))),
            Cvoid, Tuple{Ptr{$T}, Vec{$W,$T}, Vec{$W,$I}, $U}, ptr, v, i, mask
        )
    end
end

for storeop ∈ [:vstore!, :vnoaliasstore!]

    @eval @inline $storeop(ptr::Ptr{T}, v::AbstractSIMDVector{W,T}, i::AbstractStructVec{W,I}) where {W,T,I<:Integer} = $storeop(ptr, extract_data(v), extract_data(i), Val{false}())
    @eval @inline $storeop(ptr::Ptr{T}, v::AbstractSIMDVector{W,T}, i::AbstractStructVec{W,I}, mask::Mask{W}) where {W,T,I<:Integer} = $storeop(ptr, extract_data(v), extract_data(i), mask.u, Val{false}())
    @eval @inline $storeop(ptr::Ptr{T}, v::AbstractSIMDVector{W,T}, i::AbstractStructVec{W,I}, mask::Unsigned) where {W,T,I<:Integer} = $storeop(ptr, extract_data(v), extract_data(i), mask, Val{false}())

    @eval @inline $storeop(ptr::Ptr{T}, v::AbstractSIMDVector{W,T}, i::AbstractStructVec{W,I}, ::Val{Aligned}) where {W,T,I<:Integer,Aligned} = $storeop(ptr, extract_data(v), extract_data(i), Val{Aligned}())
    @eval @inline $storeop(ptr::Ptr{T}, v::AbstractSIMDVector{W,T}, i::AbstractStructVec{W,I}, mask::Mask{W}, ::Val{Aligned}) where {W,T,I<:Integer,Aligned} = $storeop(ptr, extract_data(v), extract_data(i), mask.u, Val{Aligned}())
    @eval @inline $storeop(ptr::Ptr{T}, v::AbstractSIMDVector{W,T}, i::AbstractStructVec{W,I}, mask::Unsigned, ::Val{Aligned}) where {W,T,I<:Integer,Aligned} = $storeop(ptr, extract_data(v), extract_data(i), mask, Val{Aligned}())

    @eval @inline $storeop(ptr::Ptr{T}, v::AbstractSIMDVector{W,T}, i::AbstractStructVec{W,I}, ::Val{Aligned}, ::Val{false}) where {W,T,I<:Integer,Aligned} = $storeop(ptr, extract_data(v), extract_data(i), Val{Aligned}())
    @eval @inline $storeop(ptr::Ptr{T}, v::AbstractSIMDVector{W,T}, i::AbstractStructVec{W,I}, mask::Mask{W}, ::Val{Aligned}, ::Val{false}) where {W,T,I<:Integer,Aligned} = $storeop(ptr, extract_data(v), extract_data(i), mask.u, Val{Aligned}())
    @eval @inline $storeop(ptr::Ptr{T}, v::AbstractSIMDVector{W,T}, i::AbstractStructVec{W,I}, mask::Unsigned, ::Val{Aligned}, ::Val{false}) where {W,T,I<:Integer,Aligned} = $storeop(ptr, extract_data(v), extract_data(i), mask, Val{Aligned}())

end

@generated function lifetime_start!(ptr::Ptr{T}, ::Val{L}) where {L,T}
    ptyp = JuliaPointerType
    decl = "declare void @llvm.lifetime.start(i64, i8* nocapture)"
    instrs = [
        "%ptr = inttoptr $ptyp %0 to i8*",
        "call void @llvm.lifetime.start(i64 $(L*sizeof(T)), i8* %ptr)",
        "ret void"
    ]
    quote
        $(Expr(:meta,:inline))
        llvmcall(
            $((decl, join(instrs, "\n"))),
            Cvoid, Tuple{Ptr{$T}}, ptr
        )
    end
end
@generated function lifetime_end!(ptr::Ptr{T}, ::Val{L}) where {L,T}
    ptyp = JuliaPointerType
    decl = "declare void @llvm.lifetime.end(i64, i8* nocapture)"
    instrs = [
        "%ptr = inttoptr $ptyp %0 to i8*",
        "call void @llvm.lifetime.end(i64 $(L*sizeof(T)), i8* %ptr)",
        "ret void"
    ]
    quote
        $(Expr(:meta,:inline))
        llvmcall(
            $((decl, join(instrs, "\n"))),
            Cvoid, Tuple{Ptr{$T}}, ptr
        )
    end
end

@inline function lifetime_start!(ptr::AbstractPointer{T}) where {T}
    lifetime_start!(pointer(ptr), Val{1}())
end
@inline function lifetime_start!(ptr::AbstractPointer{T}, ::Val{L}) where {T,L}
    lifetime_start!(pointer(ptr), Val{L}())
end
@inline function lifetime_start!(ptr::Ptr{T}) where {T}
    lifetime_start!(ptr, Val{1}())
end

@inline function lifetime_end!(ptr::AbstractPointer{T}) where {T}
   lifetime_end!(pointer(ptr), Val{1}())
end
@inline function lifetime_end!(ptr::AbstractPointer{T}, ::Val{L}) where {T,L}
    lifetime_end!(pointer(ptr), Val{L}())
end
@inline function lifetime_end!(ptr::Ptr{T}) where {T}
    lifetime_end!(ptr, Val{1}())
end
# Fallback is to do nothing
@inline lifetime_start!(::Any) = nothing
@inline lifetime_end!(::Any) = nothing

@generated function compressstore!(
    ptr::Ptr{T}, v::Vec{W,T}, mask::U
) where {W,T <: NativeTypes,U<:Unsigned}
    @assert 8sizeof(U) >= W
    ptyp = JuliaPointerType
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    mtyp_input = llvmtype(U)
    mtyp_trunc = "i$W"
    instrs = String[]
    push!(instrs, "%ptr = inttoptr $ptyp %1 to $typ*")
    if mtyp_input == mtyp_trunc
        push!(instrs, "%mask = bitcast $mtyp_input %2 to <$W x i1>")
    else
        push!(instrs, "%masktrunc = trunc $mtyp_input %2 to $mtyp_trunc")
        push!(instrs, "%mask = bitcast $mtyp_trunc %masktrunc to <$W x i1>")
    end
    decl = "declare void @llvm.masked.compressstore.$(suffix(W,T))($vtyp, $typ*, <$W x i1>)"
    push!(instrs, "call void @llvm.masked.compressstore.$(suffix(W,T))($vtyp %0, $typ* %ptr, <$W x i1> %mask)")
    push!(instrs, "ret void")
    quote
        $(Expr(:meta, :inline))
        llvmcall(
            $((decl, join(instrs, "\n"))),
            Cvoid, Tuple{Vec{$W,$T}, Ptr{$T}, $U}, v, ptr, mask
        )
    end
end
@inline compressstore!(ptr::Ptr{T}, v::Vec{W,T}, m::Mask{W}) where {W,T} = compressstore!(ptr, v, m.u)

@generated function expandload!(
    ::Type{Vec{W,T}}, ptr::Ptr{T}, mask::U
) where {W,T <: NativeTypes,U<:Unsigned}
    @assert 8sizeof(U) >= W
    ptyp = JuliaPointerType
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    vptrtyp = "<$W x $typ*>"
    mtyp_input = llvmtype(U)
    mtyp_trunc = "i$W"
    instrs = String[]
    push!(instrs, "%ptr = inttoptr $ptyp %0 to $typ*")
    if mtyp_input == mtyp_trunc
        push!(instrs, "%mask = bitcast $mtyp_input %1 to <$W x i1>")
    else
        push!(instrs, "%masktrunc = trunc $mtyp_input %1 to $mtyp_trunc")
        push!(instrs, "%mask = bitcast $mtyp_trunc %masktrunc to <$W x i1>")
    end
    decl = "declare $vtyp @llvm.masked.expandload.$(suffix(W,T))($typ*, <$W x i1>, $vtyp)"
    push!(instrs, "%res = call $vtyp @llvm.masked.expandload.$(suffix(W,T))($typ* %ptr, <$W x i1> %mask, $vtyp zeroinitializer)")
    push!(instrs, "ret $vtyp %res")
    quote
        $(Expr(:meta, :inline))
        llvmcall(
            $((decl, join(instrs, "\n"))),
            Vec{$W,$T}, Tuple{Ptr{$T}, $U}, ptr, mask
        )
    end
end
@inline expandload!(::Type{Vec{W,T}}, ptr::Ptr{T}, m::Mask{W}) where {W,T} = compressstore!(Vec{W,T}, ptr, m.u)

### LLVM syntax errors on invariants
# struct Invariant{L,T}
#     ivp::Ptr{Cvoid}
#     ptr::Ptr{T}
# end
# @generated function invariant_start!(ptr::Ptr{T}, ::Val{L}) where {L,T}
#     ptyp = JuliaPointerType
#     decls = "declare {}* @llvm.invariant.start.p0i8(i64, i8* nocapture)"
#     instrs = [
#         "%ptr = inttoptr $ptyp %0 to i8*",
#         "%ivt = call {}* @llvm.invariant.start.p0i8(i64 $(L*sizeof(T)), i8* %ptr)",
#         "%ivp = ptrtoint {}* %ivt to $ptyp",
#         "ret %ivp"
#     ]
#     quote
#         $(Expr(:meta,:inline))
#         ivp = llvmcall(
#             $((decls, join(instrs, "\n"))),
#             Ptr{Cvoid}, Tuple{Ptr{$T}}, ptr
#         )
#         Invariant{$(L*sizeof(T)),T}(ivp, ptr)
#     end
# end
# @generated function invariant_end!(ivp::Invariant{L}) where {L}
#     ptyp = JuliaPointerType
#     decls = "declare void @llvm.invariant.end.p0i8({}*, i64, i8* nocapture)"
#     instrs = [
#         "%ivp = inttoptr $ptyp %0 to {}*",
#         "%ptr = inttoptr $ptyp %1 to i8*",
#         "call void @llvm.lifetime.end.p0i8({}* %ivp, i64 $(L), i8* %ptr)",
#         "ret void"
#     ]
#     quote
#         $(Expr(:meta,:inline))
#         llvmcall(
#             $((decls, join(instrs, "\n"))),
#             Cvoid, Tuple{Ptr{$T}}, ivp.ivp, ivp.ptr
#         )
#     end
# end

for store ∈ [:vstore!, :vnoaliasstore!]
    @eval @inline $store(ptr::VectorizationBase.AbstractStridedPointer{T}, v::AbstractSIMDVector{W,T}, i, b::Bool) where {W,T} = (b && $store(ptr, v, i))

    @eval @inline $store(ptr::VectorizationBase.AbstractPointer{T1}, v::AbstractStructVec{W,T2}, i::Tuple) where {W,T1,T2} = $store(ptr, vconvert(Vec{W,T1}, v), i)
    @eval @inline $store(ptr::VectorizationBase.AbstractPointer{T1}, v::AbstractStructVec{W,T2}, i::Tuple, u::Unsigned) where {W,T1,T2} = $store(ptr, vconvert(Vec{W,T1}, v), i, u)
    @eval @inline $store(ptr::VectorizationBase.AbstractPointer{T1}, v::AbstractStructVec{W,T2}, i::Tuple, u::AbstractMask{W}) where {W,T1,T2} = $store(ptr, vconvert(Vec{W,T1}, v), i, tomask(u).u)

    @eval @inline $store(ptr::VectorizationBase.AbstractPointer{T}, m::AbstractMask{W}, i::Tuple) where {W,T} = $store(ptr, vifelse(tomask(m), vone(Vec{W,T}), vzero(Vec{W,T})), i)
    @eval @inline $store(ptr::VectorizationBase.AbstractPointer{T}, m::AbstractMask{W}, i::Tuple, mask::AbstractMask{W}) where {W,T} = $store(ptr, vifelse(tomask(m), vone(Vec{W,T}), vzero(Vec{W,T})), i, tomask(mask))
    @eval @inline function $store(ptr::VectorizationBase.AbstractPointer{Bool}, m::SVec{W,Bool}, i::Tuple) where {W}
        $store(ptr.ptr, extract_data(m), VectorizationBase.offset(ptr, VectorizationBase.staticm1(i)))
    end
    @eval @inline function $store(ptr::VectorizationBase.AbstractPointer{Bool}, m::SVec{W,Bool}, i::Tuple, mask::AbstractMask{W}) where {W}
        $store(ptr.ptr, extract_data(m), VectorizationBase.offset(ptr, VectorizationBase.staticm1(i)), tomask(mask))
    end
end
@inline vstore!(ptr::Ptr{T}, v::_MM{W}, i) where {W, T <: Integer} = vstore!(ptr, vrange(_MM{W}(v.i % T)), i)
@inline vstore!(ptr::Ptr{T}, v::_MM{W}, i, m::Mask) where {W, T <: Integer} = vstore!(ptr, vrange(_MM{W}(v.i % T)), i, m)

using VectorizationBase: AbstractColumnMajorStridedPointer, PackedStridedPointer, tdot
@inline VectorizationBase.gep(ptr::AbstractColumnMajorStridedPointer, i::NTuple{W,Core.VecElement{I}}) where {W,I<:Integer} = gep(ptr.ptr, i)

@inline vadd(s::I1, v::Vec{W,I2}) where {I1<:Integer,I2<:Integer,W} = vadd(vconvert(Vec{W,I2}, s), v)

# @inline VectorizationBase.gep(ptr::AbstractColumnMajorStridedPointer, i::Tuple) = @inbounds gep(ptr.ptr, vadd(i[1], tdot(ptr.strides, Base.tail(i))))
# @inline VectorizationBase.gep(ptr::AbstractColumnMajorStridedPointer{T,0}, i::Tuple) where {T} = @inbounds gep(ptr.ptr, i[1])
@inline VectorizationBase.tdot(a::Tuple{I,Any}, b::Tuple{Vec{W,I},Any}) where {W,I} = @inbounds vmul(first(a),first(b)) + tdot(Base.tail(a),Base.tail(b))
@inline VectorizationBase.tdot(a::Tuple{I,Any}, b::Tuple{SVec{W,I},Any}) where {W,I} = @inbounds vmul(first(a),extract_data(first(b))) + tdot(Base.tail(a),Base.tail(b))
@inline VectorizationBase.tdot(a::Tuple{I}, b::Tuple{Vec{W,I}}) where {W,I} = @inbounds vmul(first(a),first(b))
@inline VectorizationBase.tdot(a::Tuple{I}, b::Tuple{SVec{W,I}}) where {W,I} = @inbounds vmul(first(a),extract_data(first(b)))

@inline function VectorizationBase.tdot(a::Tuple{I1,Any}, b::Tuple{Vec{W,I2},Any}) where {W,I1,I2}
    @inbounds vadd(vmul(first(a) % I2,first(b)), vconvert(Vec{W,I2},tdot(Base.tail(a),Base.tail(b))))
end
@inline function VectorizationBase.tdot(a::Tuple{I1,Any}, b::Tuple{SVec{W,I2},Any}) where {W,I1,I2}
    @inbounds vadd(vmul(first(a) % I2,extract_data(first(b))), vconvert(Vec{W,I2},tdot(Base.tail(a),Base.tail(b))))
end
@inline function VectorizationBase.tdot(a::Tuple{I1}, b::Tuple{Vec{W,I2}}) where {W,I1,I2}
    @inbounds vmul(first(a) % I2,first(b))
end
@inline function VectorizationBase.tdot(a::Tuple{I1}, b::Tuple{SVec{W,I2}}) where {W,I1,I2}
    @inbounds vmul(first(a) % I2,extract_data(first(b)))
end

# _MM support
# zero initialized
# scalar if only and Int

for store ∈ [:vstore!, :vnoaliasstore!]
    @eval @inline $store(ptr::Ptr{T}, v::S, i::Union{SVec{W,<:Integer},_MM{W}}) where {W,T<:Number,S<:Number} = $store(ptr, vbroadcast(Vec{W,T}, v), i)
    @eval @inline $store(ptr::Ptr{T}, v::S, i::Union{SVec{W,<:Integer},_MM{W}}, u::Unsigned) where {W,T<:Number,S<:Number} = $store(ptr, vbroadcast(Vec{W,T}, v), i, u)
    @eval @inline $store(ptr::Ptr{T}, v::S, i::Union{SVec{W,<:Integer},_MM{W}}, u::Mask{W}) where {W,T<:Number,S<:Number} = $store(ptr, vbroadcast(Vec{W,T}, v), i, u.u)
end

vectypewidth(::Type{V}) where {W, V<:AbstractSIMDVector{W}} = W::Int
vectypewidth(::Type{_MM{W}}) where {W} = W::Int
vectypewidth(::Any) = 1

@inline vload(r::AbstractRange, i::Tuple{_MM{W,T}}) where {W,T} = SVec(vadd(vrangemul(Val{W}(), step(r), Val{0}()), @inbounds r[vadd(i[1].i, one(T))]))
@inline vload(r::UnitRange, i::Tuple{_MM{W,T}}) where {W,T} = @inbounds(_MM{W}(r[vadd(i[1].i, one(T))]))
# Ignore masks
@inline vload(r::AbstractRange, i::Tuple{_MM{W,T}}, ::Unsigned) where {W,T} = SVec(vadd(vrangemul(Val{W}(), step(r), Val{0}()), @inbounds r[vadd(i[1].i, one(T))]))
@inline vload(r::UnitRange, i::Tuple{_MM{W,T}}, ::Unsigned) where {W,T} = @inbounds(_MM{W}(r[vadd(i[1].i, one(T))]))
@inline vload(r::AbstractRange, i::Tuple{_MM{W,T}}, ::Mask) where {W,T} = SVec(vadd(vrangemul(Val{W}(), step(r), Val{0}()), @inbounds r[vadd(i[1].i, one(T))]))
@inline vload(r::UnitRange, i::Tuple{_MM{W,T}}, ::Mask) where {W,T} = @inbounds(_MM{W}(r[vadd(i[1].i, one(T))]))

function transposeshuffle0(split, W)
    tup = Expr(:tuple)
    w = 0
    S = 1 << split
    while w < W
        for s ∈ 0:S-1
            push!(tup.args, w + s)
        end
        for s ∈ 0:S-1
            # push!(tup.args, w + S + W + s)
            push!(tup.args, w + W + s)
        end
        w += 2S
    end
    Expr(:call, Expr(:curly, :Val, tup))
end
function transposeshuffle1(split, W)
    tup = Expr(:tuple)
    w = 0
    S = 1 << split
    while w < W
        for s ∈ 0:S-1
            push!(tup.args, w+S + s)
        end
        for s ∈ 0:S-1
            # push!(tup.args, w + W + s)
            push!(tup.args, w + S + W + s)
        end
        w += 2S
    end
    Expr(:call, Expr(:curly, :Val, tup))
end

@generated function vhaddstore!(ptr::AbstractPointer{T}, v::Tuple{V,Vararg{V,Nm1}}, i::Tuple{I,Vararg{I,Dm1}}) where {T,Nm1,W,V<:AbstractSIMDVector{W,T},Dm1,I<:Integer}
    N = Nm1 + 1; D = Dm1 + 1
    q = Expr(:block, Expr(:meta, :inline), Expr(:(=), :bptr, Expr(:call, :gep, :ptr, :i)))
    if N > 1 && ispow2(N) && ispow2(W)
        extractblock = Expr(:block)
        vectors = [Symbol(:v_, n) for n ∈ 0:N-1]
        for n ∈ 1:N
            push!(extractblock.args, Expr(:(=), vectors[n], Expr(:ref, :v, n)))
        end
        push!(q.args, Expr(:macrocall, Symbol("@inbounds"), LineNumberNode(@__LINE__, Symbol(@__FILE__)), extractblock))
        ncomp = 0
        minWN = min(W,N)
        while ncomp < N
            Nt = minWN;
            Wt = W
            splits = 0
            while Nt > 1
                Nt >>>= 1
                shuffle0 = transposeshuffle0(splits, Wt)
                shuffle1 = transposeshuffle1(splits, Wt)
                splits += 1
                for nh ∈ 1:Nt
                    n1 = 2nh
                    n0 = n1 - 1
                    v0 = vectors[n0 + ncomp]; v1 = vectors[n1 + ncomp]; vh = vectors[nh + ncomp];
                    # combine n0 and n1
                    push!(q.args, Expr(
                        :(=), vh, Expr(
                            :call, :vadd,
                            Expr(:call, :shufflevector, v0, v1, shuffle0),
                            Expr(:call, :shufflevector, v0, v1, shuffle1))
                    )
                          )
                end
            end
            # v0 is now the only vector
            v0 = vectors[ncomp + 1]
            while Wt > minWN
                Wh = Wt >>> 1
                push!(q.args, Expr(
                    :(=), v0, Expr(
                        :call, :vadd,
                        Expr(:call, :shufflevector, v0, Expr(:call, Expr(:curly, :Val, Expr(:tuple, [w for w ∈ 0:Wh-1]...)))),
                        Expr(:call, :shufflevector, v0, Expr(:call, Expr(:curly, :Val, Expr(:tuple, [w for w ∈ Wh:Wt-1]...)))))
                )
                      )
                Wt = Wh
            end
            push!(q.args, Expr(:call, :vstore!, :bptr, v0))
            ncomp += minWN
        end
    else
        for n ∈ 1:N
            push!(
                q.args,
                Expr(
                    :call, :vstore!,
                    Expr(:call, :gep, :bptr, Expr(:tuple, n-1)),
                    Expr(
                        :call, :vsum,
                        Expr(:macrocall, Symbol("@inbounds"), LineNumberNode(@__LINE__, Symbol(@__FILE__)), Expr(:ref, :v, n))
                    )
                )
            )
        end
    end
    q
end

using VectorizationBase: MappedStridedPointer
@inline function vload(::Type{Vec{W,T}}, m::MappedStridedPointer{F,T}) where {W,F,T}
    extract_data(m.f(vload(SVec{W,T}, m.ptr)))
end
@inline function vload(::Type{Vec{W,T}}, m::MappedStridedPointer{F,T}, i) where {W,F,T}
    extract_data(m.f(vload(SVec{W,T}, m.ptr, i)))
end
@inline function vload(::Type{Vec{W,T}}, m::MappedStridedPointer{F,T}, mask::Union{Unsigned,Mask{W,<:Unsigned}}) where {W,F,T}
    extract_data(m.f(vload(SVec{W,T}, m.ptr, mask)))
end
@inline function vload(::Type{Vec{W,T}}, m::MappedStridedPointer{F,T}, i, mask::Union{Unsigned,Mask{W,<:Unsigned}}) where {W,F,T}
    extract_data(m.f(vload(SVec{W,T}, m.ptr, i, mask)))
end
@inline function vload(::Type{SVec{W,T}}, m::MappedStridedPointer{F,T}) where {W,F,T}
    m.f(vload(SVec{W,T}, m.ptr))
end
@inline function vload(::Type{SVec{W,T}}, m::MappedStridedPointer{F,T}, i) where {W,F,T}
    m.f(vload(SVec{W,T}, m.ptr, i))
end
@inline function vload(::Type{SVec{W,T}}, m::MappedStridedPointer{F,T}, mask::Union{Unsigned,Mask{W,<:Unsigned}}) where {W,F,T}
    m.f(vload(SVec{W,T}, m.ptr, mask))
end
@inline function vload(::Type{SVec{W,T}}, m::MappedStridedPointer{F,T}, i, mask::Union{Unsigned,Mask{W,<:Unsigned}}) where {W,F,T}
    m.f(vload(SVec{W,T}, m.ptr, i, mask))
end
@inline function vload(::Val{W}, m::MappedStridedPointer{F,T}) where {W,F,T}
    m.f(vload(SVec{W,T}, m.ptr))
end
@inline function vload(::Val{W}, m::MappedStridedPointer{F,T}, i) where {W,F,T}
    m.f(vload(SVec{W,T}, m.ptr, i))
end
@inline function vload(::Val{W}, m::MappedStridedPointer{F,T}, mask::Union{Unsigned,Mask{W,<:Unsigned}}) where {W,F,T}
    m.f(vload(SVec{W,T}, m.ptr, mask))
end
@inline function vload(::Val{W}, m::MappedStridedPointer{F,T}, i, mask::Union{Unsigned,Mask{W,<:Unsigned}}) where {W,F,T}
    m.f(vload(SVec{W,T}, m.ptr, i, mask))
end

@generated function vload(ptr::Ptr{Bool}, i::_MM{W,I}) where {W,I<:Integer}
    U = VectorizationBase.mask_type(W)
    utype = "i$(8sizeof(U))"
    itype = "i$(8sizeof(I))"
    ptyp = "i$(8sizeof(Int))"
    instrs = String[]
    push!(instrs, "%ptrbool = inttoptr $ptyp %0 to i8*")
    push!(instrs, "%ptroffset = getelementptr inbounds i8, i8* %ptrbool, $itype %1")
    push!(instrs, "%ptrvbool = bitcast i8* %ptroffset to <$W x i8>*")
    push!(instrs, "%vbool = load <$W x i8>, <$W x i8>* %ptrvbool")
    push!(instrs, "%vbooltrunc = trunc <$W x i8> %vbool to <$W x i1>")
    if 8sizeof(U) == W
        push!(instrs, "%mask = bitcast <$W x i1> %vbooltrunc to $utype")
    else
        push!(instrs, "%masktrunc = bitcast <$W x i1> %vbooltrunc to i$(W)")
        push!(instrs, "%mask = zext i$(W) %masktrunc to $utype")
    end
    push!(instrs, "ret $utype %mask")
    quote
        $(Expr(:meta,:inline))
        Mask{$W}(llvmcall(
            $(join(instrs,"\n")),
            $U,
            Tuple{Ptr{Bool},$I},
            ptr, i.i
        ))
    end
end

@generated function vstore!(ptr::Ptr{Bool}, v::Mask{W,U}, i::I) where {W,U,I<:Integer}
    utype = "i$(8sizeof(U))"
    itype = "i$(8sizeof(I))"
    ptyp = "i$(8sizeof(Int))"
    instrs = String[]
    push!(instrs, "%ptrbool = inttoptr $ptyp %0 to i8*")
    push!(instrs, "%ptroffset = getelementptr inbounds i8, i8* %ptrbool, $itype %2")
    # push!(instrs, "%ptr = bitcast i8* %ptroffset to <$(8W) x i1>*")
    push!(instrs, "%ptr = bitcast i8* %ptroffset to <$(W) x i8>*")
    if W == 8sizeof(U)
        push!(instrs, "%mask = bitcast $utype %1 to <$W x i1>")
    else
        push!(instrs, "%masktrunc = trunc $utype %1 to i$(W)")
        push!(instrs, "%mask = bitcast i$(W) %masktrunc to <$W x i1>")
    end
    push!(instrs, "%maskzext = zext <$W x i1> %mask to <$W x i8>")
    # push!(instrs, "%vbits = bitcast <$W x i8> %maskzext to <$(8W) x i1>")
    # undefmask = ""
    # for w in 1:W
    #     for b in 1:7
    #         undefmask *= " i1 undef,"
    #     end
    #     undefmask *= w == W ? " i1 1" : " i1 1,"
    # end
    # push!(instrs, "%vundefbits = and <$(8W) x i1> %vbits, <$undefmask >")
    push!(instrs, "store <$(W) x i8> %maskzext, <$(W) x i8>* %ptr")
    push!(instrs, "ret void")
    quote
        $(Expr(:meta,:inline))
        llvmcall(
            $(join(instrs,"\n")),
            Cvoid,
            Tuple{Ptr{Bool}, $U, $I},
            ptr, v.u, i
        )
    end
end


text:
module PGFPlots

export LaTeXString, @L_str, @L_mstr
export plot, tikzCode, ErrorBars, Axis, Axes, PolarAxis, SmithAxis, TernaryAxis, GroupPlot, Plots, ColorMaps, save, define_color
export pushPGFPlotsOptions, popPGFPlotsOptions, resetPGFPlotsOptions, pgfplotsoptions
export pushPGFPlotsPreamble, popPGFPlotsPreamble, resetPGFPlotsPreamble, pgfplotspreamble
export pushPGFPlots, popPGFPlots
import Colors: RGB
import Contour: contours, levels

using Requires
using Discretizers
using Printf

# load dynamic dependencies
function __init__()
    @require DataFrames="a93c6f00-e57d-5684-b7b6-d8193f3e46c0" include("dataframes.jl")
end

mutable struct ErrorBars
    data::AbstractMatrix{Real}
    style
    mark
    ErrorBars(data::AbstractMatrix{T}; style=nothing, mark=nothing) where {T <: Real} = new(data, style, mark)
end

mylength(x) = (x == nothing) ? 0 : length(x)

function ErrorBars(; x=nothing, y=nothing, xplus=nothing, xminus=nothing, yplus=nothing, yminus=nothing, style=nothing, mark=nothing)
    if x != nothing
        xplus = x
        xminus = x
    end
    if y != nothing
        yplus = y
        yminus = y
    end
    n = maximum(map(mylength, Any[xplus, xminus, yplus, yminus]))
    xplus = mylength(xplus) == n ? xplus : zeros(n)
    xminus = mylength(xminus) == n ? xminus : zeros(n)
    yplus = mylength(yplus) == n ? yplus : zeros(n)
    yminus = mylength(yminus) == n ? yminus : zeros(n)
    data = hcat(xplus, xminus, yplus, yminus)'
    ErrorBars(data, style=style, mark=mark)
end

include("colormaps.jl")
include("plots.jl")

import TikzPictures: TikzPicture, PDF, TEX, TIKZ, SVG, save, LaTeXString, @L_str, @L_mstr

_pgfplotsoptions = Any[""]

pushPGFPlotsOptions(options::AbstractString) = push!(_pgfplotsoptions, options)
function popPGFPlotsOptions()
    if length(_pgfplotsoptions) > 1
        pop!(_pgfplotsoptions)
    end
end
resetPGFPlotsOptions() = deleteat!(_pgfplotsoptions, 2:length(_pgfplotsoptions))

function pushPGFPlots(options::AbstractString)
    warn("Use pushPGFPlotsOptions")
    pushPGFPlotsOptions(options)
end

function popPGFPlots()
    warn("Use popPGFPlotsOptions")
    popPGFPlotsOptions()
end

pgfplotsoptions() = join(_pgfplotsoptions, ",\n")

_pgfplotspreamble = Any[read(joinpath(dirname(@__FILE__), "preamble.tex"),String)]

pushPGFPlotsPreamble(preamble::AbstractString) = push!(_pgfplotspreamble, preamble)
function popPGFPlotsPreamble()
    if length(_pgfplotspreamble) > 1
        pop!(_pgfplotspreamble)
    end
end
resetPGFPlotsPreamble() = deleteat!(_pgfplotspreamble,2:length(_pgfplotspreamble))

pgfplotspreamble() = join(_pgfplotspreamble, "\n")

function define_color(name::AbstractString, color::RGB)
    _pgfplotspreamble[end] = _pgfplotspreamble[end] * "\n\\definecolor{$name}{rgb}{$(color.r), $(color.g), $(color.b)}"
end

define_color(name::AbstractString, color) = define_color(name, convert(RGB, color))

function define_color(name::AbstractString, color::Vector{T}) where {T<:AbstractFloat}
    if length(color) == 3
        r, g, b = color[1], color[2], color[3]
        _pgfplotspreamble[end] = _pgfplotspreamble[end] * "\n\\definecolor{$name}{rgb}{$r, $g, $b}"
    elseif length(color) == 4
        c, m, y, k = color[1], color[2], color[3], color[4]
        _pgfplotspreamble[end] = _pgfplotspreamble[end] * "\n\\definecolor{$name}{cmyk}{$c, $m, $y, $k}"
    else
        error("Color must have three or four components")
    end
end

function define_color(name::AbstractString, color::Vector{T}) where {T<:Integer}
    @assert(length(color) == 3, "Color must have three components")
    r, g, b = color[1], color[2], color[3]
    _pgfplotspreamble[end] = _pgfplotspreamble[end] * "\n\\definecolor{$name}{RGB}{$r, $g, $b}"
end

function define_color(name::AbstractString, color::UInt32)
    _pgfplotspreamble[end] = _pgfplotspreamble[end] * "\n\\definecolor{$name}{HTML}{$(uppercase(string(color, base=16)))}"
end

function define_color(name::AbstractString, color::Real)
    _pgfplotspreamble[end] = _pgfplotspreamble[end] * "\n\\definecolor{$name}{gray}{$color}"
end

histogramMap = Dict(
    :bins => "bins",
    :density => "density",
    :cumulative => "cumulative"
    )

linearMap = Dict(
    :mark => "mark",
    :markSize => "mark size",
    :style => "",
    :onlyMarks => "only marks"
    )

barMap = Dict(
    :style => ""
    )

scatterMap = Dict(
    :mark => "mark",
    :markSize => "mark size",
    :style => "",
    :onlyMarks => "only marks",
    :scatterClasses => "scatter/classes"
    )

errorbarsMap = Dict(
    :mark => "error mark",
    :style => "error bar style"
    )

quiverMap = Dict(
    :style => ""
    )

contourMap = Dict(
    :number => "number",
    :levels => "levels",
    :style => "",
    :labels => "labels"
    )

patch2DMap = Dict(
    :style => "",
    :patch_type => "patch type",
    :shader => "shader",
    :legendentry => "legendentry"
    )

using .Plots
using .ColorMaps

const IntegerRange = Tuple{Integer,Integer}
const RealRange = Tuple{Real,Real}

mutable struct Axis
    plots::Vector{Plot}
    title
    xlabel
    xlabelStyle
    ylabel
    ylabelStyle
    zlabel
    zlabelStyle
    xmin
    xmax
    ymin
    ymax
    axisEqual
    axisEqualImage
    enlargelimits
    axisOnTop
    view
    width
    height
    style
    legendPos
    legendStyle
    xmode
    ymode
    colorbar
    colorbarStyle
    hideAxis
    axisLines
    axisKeyword

    Axis(plots::Vector{P};title=nothing, xlabel=nothing, xlabelStyle=nothing, ylabel=nothing, ylabelStyle=nothing, zlabel=nothing, zlabelStyle=nothing, xmin=nothing, xmax=nothing,
                    ymin=nothing, ymax=nothing, axisEqual=nothing, axisEqualImage=nothing, enlargelimits=nothing, axisOnTop=nothing, view=nothing, width=nothing,
                    height=nothing, style=nothing, legendPos=nothing, legendStyle=nothing, xmode=nothing, ymode=nothing, colorbar=nothing, colorbarStyle=nothing, hideAxis=nothing, axisLines=nothing, axisKeyword="axis") where {P <: Plot} =
        new(plots, title, xlabel, xlabelStyle, ylabel, ylabelStyle, zlabel, zlabelStyle, xmin, xmax, ymin, ymax, axisEqual, axisEqualImage, enlargelimits, axisOnTop, view, width, height, style, legendPos, legendStyle, xmode, ymode, colorbar, colorbarStyle, hideAxis, axisLines, axisKeyword
            )

    Axis(;kwargs...) = Axis(Plot[]; kwargs...)
    Axis(plot::Plot; kwargs...) = Axis(Plot[plot]; kwargs...)

    Axis(plot::Contour; kwargs...) = Axis(Plot[plot]; kwargs..., view="{0}{90}")
    Axis(plots::Vector{Contour}; kwargs...) = Axis(Plot[plots...]; kwargs..., view="{0}{90}")
end

function PolarAxis(args...; kwargs...)
    ternary_axis_string = "\\usepgfplotslibrary{polar}"
    if ternary_axis_string ∉ _pgfplotspreamble
        pushPGFPlotsPreamble(ternary_axis_string)
    end
    return Axis(args...; kwargs..., axisKeyword = "polaraxis")
end

function TernaryAxis(args...; kwargs...)
    ternary_axis_string = "\\usepgfplotslibrary{ternary}"
    if ternary_axis_string ∉ _pgfplotspreamble
        pushPGFPlotsPreamble(ternary_axis_string)
    end
    return Axis(args...; kwargs..., axisKeyword = "ternaryaxis")
end

function SmithAxis(args...; kwargs...)
    ternary_axis_string = "\\usepgfplotslibrary{smithchart}"
    if ternary_axis_string ∉ _pgfplotspreamble
        pushPGFPlotsPreamble(ternary_axis_string)
    end
    return Axis(args...; kwargs..., axisKeyword = "smithchart")
end

const Axes = Vector{Axis}

function Base.push!(g::Axis, p::Plot)
    push!(g.plots, p)
end

axisMap = Dict(
    :title => "title",
    :xlabel => "xlabel",
    :xlabelStyle => "xlabel style",
    :ylabel => "ylabel",
    :ylabelStyle => "ylabel style",
    :zlabel => "zlabel",
    :zlabelStyle => "zlabel style",
    :xmin => "xmin",
    :xmax => "xmax",
    :ymin => "ymin",
    :ymax => "ymax",
    :axisEqual => "axis equal",
    :axisEqualImage => "axis equal image",
    :enlargelimits => "enlargelimits",
    :axisOnTop => "axis on top",
    :view => "view",
    :width => "width",
    :height => "height",
    :style => "",
    :legendPos => "legend pos",
    :legendStyle => "legend style",
    :xmode => "xmode",
    :ymode => "ymode",
    :colorbar => "colorbar",
    :colorbarStyle => "colorbar style",
    :hideAxis => "hide axis",
    :axisLines => "axis lines"
    )

mutable struct GroupPlot
    axes::AbstractArray{Axis,1}
    dimensions::IntegerRange
    style
    groupStyle
    GroupPlot(;style=nothing,groupStyle=nothing) = new(AbstractArray{Axis,1}[], (0,0), style, groupStyle)
    GroupPlot(axes::AbstractArray{Axis,1};style=nothing,groupStyle=nothing) = new(axes, (length(axes), 1), style, groupStyle)
    GroupPlot(plots;style=nothing,groupStyle=nothing) = new([Axis(p) for p in plots], (length(plots), 1), style, groupStyle)
    GroupPlot(dimensions::IntegerRange;style=nothing,groupStyle=nothing) = new(AbstractArray{Axis,1}[], dimensions, style, groupStyle)
    GroupPlot(rows::Integer, columns::Integer;style=nothing,groupStyle=nothing) = new(AbstractArray{Axis,1}[], (rows, columns), style, groupStyle)
end

function Base.push!(g::GroupPlot, a::Axis)
    push!(g.axes, a)
    if length(g.axes) > prod(g.dimensions)
        g.dimensions = (length(g.axes), 1)
    end
    g
end
Base.push!(g::GroupPlot, p::Plot) = push!(g, Axis(p))
function Base.append!(g::GroupPlot, arr::AbstractVector{T}) where {T<:Union{Plot,Axis}}
    for a in arr
        push!(g, a)
    end
    g
end

function printList(o::IO, a::AbstractVector; brackets=false)
    first = true
    for elem in a
        if first
            first = false
            if brackets
                print(o, "{")
            end
            print(o, "$elem")
        else
            print(o, ", $elem")
        end
    end
    if !first && brackets
        print(o, "}")
    end
end

function nextOptionHelper(o::IO, isFirst::Bool, brackets::Bool, breakLine::Bool=true)
    if isFirst && brackets
        print(o, "[\n  ")
    elseif !isFirst && breakLine
        print(o, ",\n  ")
    elseif !isFirst
        print(o, ", ")
    end
    return false # used to set isFirst=false
end

function optionHelper(o::IO, m, object; brackets::Bool=true, preOptions::Vector{T}=String[], postOptions::Vector{T}=String[], wrapIn::String="") where T <: AbstractString
    isFirst = true
    for t in preOptions
        isFirst = nextOptionHelper(o, isFirst, brackets)
        print(o, t)
    end
    if length(wrapIn) > 0
        nextOptionHelper(o, isFirst, brackets)
        print(o, "$wrapIn = {")
        isFirst = true # temporarily true inside brackets
    end
    for (sym, str) in m
        if getfield(object, sym) !== nothing && getfield(object, sym) != ""
            isFirst = nextOptionHelper(o, isFirst, length(wrapIn)==0 && brackets, length(wrapIn)==0)
            if length(str) > 0
                print(o, "$str = {$(getfield(object, sym))}") # wrap with keyword
            else
                print(o, string(getfield(object, sym))) # print directly, without keyword
            end
        end
    end
    if length(wrapIn) > 0
        print(o, "}")
        isFirst = false
    end
    for t in postOptions
        isFirst = nextOptionHelper(o, isFirst, brackets)
        print(o, t)
    end
    if !isFirst && brackets
        print(o, "\n]")
    end
end

function plotHelper(o::IO, p::Plots.Histogram)
    if (p.discretization == :pgfplots && p.bins > 0) ||
       (p.discretization == :default && length(p.data) ≤ Plots.THRESHOLD_NSAMPLES_DISC_OURSELVES)

        print(o, "\\addplot+")
        optionHelper(o, histogramMap, p; preOptions=["mark = none", p.style], wrapIn="hist")
        println(o, " table[\n  row sep=\\\\,\n  y index = 0\n] { data \\\\")
        for d in p.data
            println(o, "  $d \\\\ ")
        end
        println(o, "};")

    else
        # discretize using Discretizers.jl

        if p.discretization == :specified && p.bins > 0
            edges = binedges(DiscretizeUniformWidth(p.bins), convert(Vector{Float64}, p.data))
        else
            discretization = p.discretization == :default ? :auto : p.discretization
            edges = binedges(DiscretizeUniformWidth(discretization), convert(Vector{Float64}, p.data))
        end

        linear = Plots._construct_histogram_linear_data(p.data, edges, p.density, p.cumulative)
        if p.style != "fill=blue!10"
            linear.style = p.style
            if !occursin("ybar interval", linear.style)
                linear.style = "ybar interval," * linear.style
            end
        end
        plotHelper(o, linear)
    end
end

function plotHelper(o::IO, p::BarChart)
    print(o, "\\addplot+")
    if p.errorBars === nothing
        optionHelper(o, barMap, p)
        println(o, " coordinates {")
        for (k,v) in zip(p.keys, p.values)
            println(o, "  ($k, $v)")
        end
        println(o, "};")
    else
        plotHelperErrorBars(o, p)
    end
end
function PGFPlots.Axis(p::BarChart; kwargs...)
    # TODO : What's a better way to do this?
    style = "ybar = 0pt,\n  bar width = 18pt,\n  xtick = data,\n  symbolic x coords = {$(Plots.symbolic_x_coords(p))}"
    kwargs_unsplat = isempty(kwargs) ? Array{Pair{Symbol,Any}}(undef,0) : convert(Array{Pair{Symbol,Any}},collect(kwargs))
    i = findfirst(tup->tup[1] == :style, kwargs_unsplat)
    if i !== nothing
        kwargs_unsplat[i] = :style=>(style * ", " * kwargs_unsplat[i][2])
    else
        push!(kwargs_unsplat, :style=>style)
    end
    i = findfirst(tup->tup[1] == :ymin, kwargs_unsplat)
    if i === nothing
        push!(kwargs_unsplat, :ymin=>0)
    end

    return Axis(Plots.Plot[p]; kwargs_unsplat...)
end

# todo: add error bars style
function plotHelperErrorBars(o::IO, p::Linear)
    print(o, "[\n  ")
    optionHelper(o, linearMap, p; brackets=false)
    print(o, ",\n  error bars/.cd,\n  ")
    optionHelper(o, errorbarsMap, p.errorBars; brackets=false, postOptions=["x dir=both", "x explicit", "y dir=both", "y explicit"])
    println(o, "\n] table[")
    println(o, "  x error plus=ex+,\n  x error minus=ex-,\n  y error plus=ey+,\n  y error minus=ey-")
    println(o, "] {")
    println(o, "  x y ex+ ex- ey+ ey-")
    x = vcat(p.data, p.errorBars.data)
    for i = 1:size(p.data, 2)
        println(o, "  $(x[1,i]) $(x[2,i]) $(x[3,i]) $(x[4,i]) $(x[5,i]) $(x[6,i])")
    end
    if p.closedCycle
        println(o, "} \\closedcycle;")
    else
        println(o, "};")
    end
end
# todo there is a lot of code redundancy
function plotHelperErrorBars(o::IO, p::BarChart)
    print(o, "[\n  ")
    optionHelper(o, barMap, p; brackets=false)
    print(o, ",\n  error bars/.cd,\n  ")
    optionHelper(o, errorbarsMap, p.errorBars; brackets=false, postOptions=["x dir=both", "x explicit", "y dir=both", "y explicit"])
    println(o, "\n] table[")
    println(o, "  x error plus=ex+,\n  x error minus=ex-,\n  y error plus=ey+,\n  y error minus=ey-")
    println(o, "] {")
    println(o, "  x y ex+ ex- ey+ ey-")
    x = p.errorBars.data
    for i = 1:length(p.values)
        println(o, "  $(p.keys[i]) $(p.values[i]) $(x[1,i]) $(x[2,i]) $(x[3,i]) $(x[4,i])")
    end
    println(o, "};")
end

function plotHelper(o::IO, p::Linear)
    print(o, "\\addplot+")
    if p.errorBars === nothing
        optionHelper(o, linearMap, p)
        println(o, " coordinates {")
        for i in 1:size(p.data,2)
            println(o, "  ($(p.data[1,i]), $(p.data[2,i]))")
        end
        if p.closedCycle
            println(o, "} \\closedcycle;")
        else
            println(o, "};")
        end
    else
        plotHelperErrorBars(o, p)
    end
end

function plotHelper(o::IO, p::SmithData)
    print(o, "\\addplot+")
    optionHelper(o, linearMap, p)
    println(o, " coordinates {")
    for item in p.data
        println(o, "  ($(real(item)), $(imag(item)))")
    end
    println(o, "};")
end

function plotHelper(o::IO, p::SmithCircle)
    if p.style !== nothing
        println(o, "\\draw[$(p.style)] ($(p.xc), $(p.yc)) circle [radius=$(p.radius)cm];")
    else
        println(o, "\\draw ($(p.xc), $(p.yc)) circle [radius=$(p.radius)cm];")
    end
end

function plotHelper(o::IO, p::Scatter)
    print(o, "\\addplot+")
    options = ["scatter", "scatter src = explicit symbolic"]
    if size(p.data,1) == 2
        options = ["draw = none"]
        p.onlyMarks = nothing
    elseif p.scatterClasses === nothing
        options = ["scatter", "scatter src = explicit"]
    end
    optionHelper(o, scatterMap, p; preOptions=options)
    println(o, " coordinates {")
    if size(p.data,1) == 2
        for i in 1:size(p.data,2)
            println(o, "  ($(p.data[1,i]), $(p.data[2,i]))")
        end
    else
        for i in 1:size(p.data,2)
            println(o, "  ($(p.data[1,i]), $(p.data[2,i])) [$(p.data[3,i])]")
        end
    end
    println(o, "};")
end

# Specific version for Linear3 mutable struct
# Changes are addplot3 (vs addplot) and iterate over all 3 columns
function plotHelper(o::IO, p::Linear3)
    print(o, "\\addplot3+")
    optionHelper(o, linearMap, p)
    println(o, " coordinates {")
    for i = 1:size(p.data,2)
        println(o, "  ($(p.data[1,i]), $(p.data[2,i]), $(p.data[3,i]))")
    end
    println(o, "};")
end

function plotHelper(o::IO, p::Node)
    axis = p.axis !== nothing ? p.axis : "axis cs"
    if p.style !== nothing
        println(o, "\\node at ($(axis):$(p.x), $(p.y)) [$(p.style)] {$(p.data)};")
    else
        println(o, "\\node at ($(axis):$(p.x), $(p.y)) {$(p.data)};")
    end
end

function plotHelper(o::IO, p::ErrorBars)
    print(o, "\\addplot+")
    optionHelper(o, errorbarsMap, p; postOptions=["error bars/.cd", "  x dir = both", "  x explicit", "  y dir = both", "  y explicit"])
    println(o," coordinates {")
    for i = 1:size(p.data,2)
        println(o, "  ($(p.data[1,i]), $(p.data[2,i])) +=($(p.data[3,i]),$(p.data[4,i])) -=($(p.data[5,i]),$(p.data[6,i]))")
    end
    println(o, "};")
end

function plotHelper(o::IO, p::Quiver)
    print(o, "\\addplot+")
    optionHelper(o, quiverMap, p; postOptions=["quiver = {u=\\thisrow{u}, v=\\thisrow{v}}"])
    println(o, " table {")
    println(o, "  x y u v")
    for i = 1:size(p.data,2)
        println(o, "  $(p.data[1,i]) $(p.data[2,i]) $(p.data[3,i]) $(p.data[4,i])")
    end
    println(o, "};")
end

function plotHelper(o::IO, p::Contour)
    arg = 5
    if p.number !== nothing
        arg = p.number
    elseif p.levels !== nothing
        arg = p.levels
    end
    C = contours(convert(Vector{Float64}, p.xbins), convert(Vector{Float64}, p.ybins), convert(Matrix{Float64}, p.data), arg)

    print(o, "\\addplot3[\n  contour prepared")
    if p.contour_style !== nothing
        print(o, "={$(p.contour_style)}")
    elseif p.labels == false
        print(o, "={labels=false}")
    end
    if p.style !== nothing
        print(o, ",\n  $(p.style)")
    end
    println(o, "\n] table {")

    for c in levels(C)
        level = c.level
        for l in c.lines
            for v in l.vertices
                println(o, "  $(v[1]) $(v[2]) $level")
            end
            println(o)
        end
    end
    println(o, "};")
end

function plotHelper(o::IO, p::Circle)
    if p.style !== nothing
        println(o, "\\draw[$(p.style)] (axis cs:$(p.xc), $(p.yc)) circle[radius=$(p.radius)];")
    else
        println(o, "\\draw (axis cs:$(p.xc), $(p.yc)) circle[radius=$(p.radius)];")
    end
end

function plotHelper(o::IO, p::Ellipse)
    if p.style !== nothing
        println(o, "\\draw[$(p.style)] (axis cs:$(p.xc), $(p.yc)) ellipse[x radius=$(p.xradius), y radius=$(p.yradius)];")
    else
        println(o, "\\draw (axis cs:$(p.xc), $(p.yc)) ellipse[x radius=$(p.xradius), y radius=$(p.yradius)];")
    end
end

plotHelper(o::IO, p::Command) = println(o, p.cmd * ";")

function plotHelper(o::IO, p::Image)
    if p.zmin == p.zmax
        error("Your colorbar range limits must not be equal to each other.")
    end
    print(o, "\\addplot[\n  ")
    if p.style !== nothing
        println(o, join([p.style, "point meta min = $(p.zmin)", "point meta max = $(p.zmax)"], ",\n  "))
    else
        println(o, join(["point meta min = $(p.zmin)", "point meta max = $(p.zmax)"], ",\n  "))
    end
    print(o, "] graphics[\n  ")
    println(o, join(["xmin = $(p.xmin)", "xmax = $(p.xmax)", "ymin = $(p.ymin)", "ymax = $(p.ymax)"], ",\n  "))
    println(o, "] {$(p.filename)};")
end

function plotHelper(o::IO, p::Patch2D)
    print(o, "\\addplot")
    optionHelper(o, patch2DMap, p)

    m = p.patch_type == "rectangle" ? 4 : 3
    if size(p.data, 1) == 3 # include color
        println(o, " table[\n  point meta = \\thisrow{c}\n] {")
        println(o, "  x y c")
    elseif size(p.data, 1) == 2
        println(o, " table {")
        println(o, "  x y")
    else
        error("Incorrect dimensions of matrix passed to Patch2D")
    end
    for i in 1 : size(p.data, 2)
        @printf(o, "  %g %g", p.data[1,i], # x
                              p.data[2,i]) # y
        if size(p.data, 1) > 2
            @printf(o, " %g\n", p.data[3,i]) # color
        else
            println(o)
        end

        if mod(i, m) == 0
            println(o) # extra space between patches (trainges or rectangles)
        end
    end
    println(o, "};")
end

function plotHelper(o::IO, p::MatrixPlot)
    if p.zmin >= p.zmax
        error("Your colorbar range limits must not be equal to each other.")
    end
    plot_options = String["point meta min = $(p.zmin)", "point meta max = $(p.zmax)"]
    if !(p.raster)
        push!(plot_options, "point meta = explicit", "matrix plot*", "mesh/cols = $(p.cols)", "mesh/rows = $(p.rows)")
    end
    if p.style !== nothing
        push!(plot_options, p.style)
    end
    print(o, "\\addplot[\n  ")
    print(o, join(plot_options, ",\n  "))
    print(o, "\n]")
    if p.raster
        print(o, " graphics[\n  ")
        println(o, join(["xmin = $(p.xmin)", "xmax = $(p.xmax)", "ymin = $(p.ymin)", "ymax = $(p.ymax)"], ",\n  "))
        println(o, "] {$(p.filename)};")
    else
        println(o, " table[\n  meta = data\n] {$(p.filename)};")
    end
end

# plot option string and contents; no \begin{axis} or \nextgroupplot
function plotHelper(o::IO, axis::Axis)
    optionHelper(o, axisMap, axis; postOptions=cat(map(axisOptions, axis.plots)...; dims=1))
    println(o, "\n") # empty line after options of \begin{axis}
    for p in axis.plots
        plotHelper(o, p)
        try
            if p.texlabel !== nothing
                println(o, "\\label{$(p.texlabel)}")
            end
        catch
        end
        println(o) # empty line between plots
    end
    legendentries = map(p->:legendentry in propertynames(p) ? p.legendentry : nothing, axis.plots)
    legendentries = vcat(legendentries...) # flatten
    # avoid adding \legend altogether if all entries are `nothing`
    if !all(legendentries .=== nothing)
        legendcontent = join(map(x->x === nothing ? "" : "{}{$x}", legendentries), ", ")
        println(o, "\\legend{$legendcontent}") # add legend
    end
end

function tikzCode(axis::Axis)
    o = IOBuffer()
    print(o, "\\begin{$(axis.axisKeyword)}")
    plotHelper(o, axis)
    print(o, "\\end{$(axis.axisKeyword)}")
    return String(take!(o))
end

function tikzCode(axes::Axes)
    o = IOBuffer()
    for axis in axes
        print(o, "\\begin{$(axis.axisKeyword)}")
        plotHelper(o, axis)
        println(o, "\\end{$(axis.axisKeyword)}")
    end
    return String(take!(o))
end

plot(ax::Union{Axis,Axes}) =
    TikzPicture(tikzCode(ax), options=pgfplotsoptions(), preamble=pgfplotspreamble())

function tikzCode(p::GroupPlot)
    o = IOBuffer()
    style = ""
    if p.style !== nothing
        style = p.style * ", "
    end
    groupStyle = ""
    if p.groupStyle !== nothing
        groupStyle = p.groupStyle * ", "
    end
    println(o, "\\begin{groupplot}[$(style)group style={$(groupStyle)group size=$(p.dimensions[1]) by $(p.dimensions[2])}]")
    for a in p.axes
        print(o, "\n\\nextgroupplot ")
        plotHelper(o, a)
    end
    println(o, "\n\\end{groupplot}")
    return String(take!(o))
end

function plot(p::GroupPlot)
    mypreamble = pgfplotspreamble() * "\\usepgfplotslibrary{groupplots}"
    TikzPicture(tikzCode(p), options=pgfplotsoptions(), preamble=mypreamble)
end

const Plottable = Union{Plot,GroupPlot,Axis,Axes,TikzPicture}

tikzCode(p::Plot) = tikzCode(Axis(p))
plot(p::Plot) = plot(Axis(p))

tikzCode(p::Contour) =
    tikzCode(Axis(p, xmin=p.xbins[1], xmax=p.xbins[end], ymin=p.ybins[1], ymax=p.ybins[end]))
plot(p::Contour) =
    plot(Axis(p, xmin=p.xbins[1], xmax=p.xbins[end], ymin=p.ybins[1], ymax=p.ybins[end]))

tikzCode(
    x::AbstractArray{A,1},
    y::AbstractArray{B,1};
    kwargs...,
    ) where {A<:Real,B<:Real} = tikzCode(Linear(x, y; kwargs...))
plot(
    x::AbstractArray{A,1},
    y::AbstractArray{B,1};
    kwargs...,
    ) where {A<:Real,B<:Real} = plot(Linear(x, y; kwargs...))

tikzCode(
    x::AbstractVector{A},
    y::AbstractVector{B},
    z::AbstractVector{C};
    kwargs...,
    ) where {A<:Real,B<:Real,C<:Real} = tikzCode(Linear3(x, y, z; kwargs...))
plot(
    x::AbstractVector{A},
    y::AbstractVector{B},
    z::AbstractVector{C};
    kwargs...,
    ) where {A<:Real,B<:Real,C<:Real} = plot(Linear3(x, y, z; kwargs...))

function Plots.Linear(f::Function, arg_range::RealRange; xbins=100, mark="none", kwargs...)
    x = range(arg_range[1], stop=arg_range[2], length=xbins)
    Linear(x, map(f, x); mark=mark, kwargs...)
end

tikzCode(f::Function, arg_range::RealRange; kwargs...) = tikzCode(Linear(f, arg_range; kwargs...))
plot(f::Function, arg_range::RealRange; kwargs...) = plot(Linear(f, arg_range; kwargs...))

plot(tkz::TikzPicture) = tkz # tikz pic doesn't need plot, here for convenience

Base.showable(::MIME"image/svg+xml", p::Plottable) = true

cleanup(p::Axis) = map(cleanup, p.plots)
cleanup(axes::Axes) = map(cleanup, axes)
cleanup(p::GroupPlot) = map(cleanup, p.axes)
cleanup(p::Plot) = nothing
cleanup(p::Circle) = nothing
cleanup(p::Ellipse) = nothing
cleanup(p::Command) = nothing
cleanup(p::Image) = rm(p.filename)
cleanup(p::MatrixPlot) = rm(p.filename)
cleanup(p::Contour) = nothing
cleanup(p::TikzPicture) = nothing

axisOptions(p::Plot) = String[]

function colormapOptions(cm::ColorMaps.GrayMap)
    if cm.invert
        return "colormap={wb}{gray(0cm)=(1); gray(1cm)=(0)}"
    else
        return "colormap={wb}{gray(0cm)=(0); gray(1cm)=(1)}"
    end
end

function colormapOptions(cm::ColorMaps.RGBArrayMap)
    o = IOBuffer()
    print(o, "colormap={mycolormap}{ ")
    n = length(cm.colors)
    for i = 1:n
        c = cm.colors[i]
        print(o, "rgb($(i-1)cm)=($(c.r),$(c.g),$(c.b)) ")
    end
    print(o, "}")
    String(take!(o))
end

function axisOptions(p::Image)
    if p.colorbar
        cmOpt = colormapOptions(p.colormap)
        if p.colorbarStyle === nothing
            return ["enlargelimits = false", "axis on top", cmOpt, "colorbar"]
        else
            return ["enlargelimits = false", "axis on top", cmOpt, "colorbar", "colorbar style = {$(p.colorbarStyle)}"]
        end
    else
        return ["enlargelimits = false", "axis on top"]
    end
end

function axisOptions(p::MatrixPlot)
    if p.colorbar
        cmOpt = colormapOptions(p.colormap)
        options = ["enlargelimits = false", "axis on top", cmOpt, "colorbar", "xmin = $(p.xmin)", "xmax = $(p.xmax)", "ymin = $(p.ymin)", "ymax = $(p.ymax)"]
        if p.colorbarStyle !== nothing
            push!(options, "style = {$(p.colorbarStyle)}")
        end
        return options
    else
        return ["enlargelimits = false", "axis on top"]
    end
end

canPlot(p::Axis) = all(map(canPlot, p.plots))
canPlot(axes::Axes) = all(map(canPlot, axes))
canPlot(p::GroupPlot) = all(map(canPlot, p.axes))
canPlot(p::Plot) = true
canPlot(p::Circle) = true
canPlot(p::Ellipse) = true
canPlot(p::Command) = true
canPlot(p::Image) = isfile(p.filename)
canPlot(p::MatrixPlot) = isfile(p.filename)
canPlot(p::Contour) = true
canPlot(p::TikzPicture) = true

function Base.show(f::IO, a::MIME"image/svg+xml", p::Plottable)
    if canPlot(p)
        r = Base.show(f, a, plot(p))
        cleanup(p)
        r
    end
end

function save(filename::AbstractString, o::Plottable; include_preamble::Bool=true, limit_to::Symbol=:default)
    _, ext = splitext(filename)
    ext = lowercase(ext)
    if ext == ".pdf"
        save(PDF(filename), plot(o))
        cleanup(o)
    elseif ext == ".svg"
        save(SVG(filename), plot(o))
        cleanup(o)
    elseif ext == ".tex"
        # limit_to requires TikzPictures v3.2.0; only use if specified
        if limit_to != :default
            save(TEX(filename; limit_to=limit_to), plot(o))
        else
            save(TEX(filename, include_preamble=include_preamble), plot(o))
        end
    elseif ext == ".tikz"
        save(TIKZ(filename), plot(o))
    elseif ext == "." || ext == ""
        error("You must specify a file extension.")
    else
        error("Unsupported file extensions: $ext")
    end
end

end # module

ext: jl


text:
__precompile__(true)
module Setfield
using MacroTools
using MacroTools: isstructdef, splitstructdef

include("lens.jl")
include("sugar.jl")
include("settable.jl")
include("experimental.jl")
end


text:
using Random: RandomDevice, randstring
using Nettle: hexdigest, gen_key32_iv16
using Primes: nextprime

const Bytes = Vector{UInt8}

abstract type RSAKey end

struct PublicRSAKey <: RSAKey
  e::BigInt
  n::BigInt
end

struct PrivateRSAKey <: RSAKey
  d::BigInt
  n::BigInt
end

"""
  `secure_rand(start, stop)`

Generates a relatively secure random number, based on the OS
"""
function secure_rand(start, stop)
  return rand(RandomDevice(), start:stop)
end

"""
  `secure_randstring(len)`

Generates a relatively secure random string, based on the OS
"""
function secure_randstring(len::Integer)::String
  return randstring(RandomDevice(), len)
end

"""
  `pack_int(num::Integer)::Bytes`

Packs an integer into an array of bytes
"""
function pack_int(num::Integer)::Bytes
  hex = string(num, base=16)
  pad = length(hex) % 2
  hex = ("0"^pad) * hex
  return hex2bytes(hex)
end

"""
  `unpack_int(b::Bytes)::BigInt`

Unpacks an array of bytes into a BigInt
"""
function unpack_int(b::Bytes)::BigInt
  hex = bytes2hex(b)
  return parse(BigInt, hex, base=16)
end

"""
  `gen_prime(num_bits::Integer)::BigInt`

Generates a random prime number with given number of bits
"""
function gen_prime(num_bits::Integer)::BigInt
  lower_bound = secure_rand(BigInt(2)^num_bits + 1, BigInt(2)^(num_bits+1))
  return nextprime(lower_bound)
end

"""
  `gen_RSAKeys(num_bits::Integer = 2048)::Tuple{PublicRSAKey, PrivateRSAKey}`

Generates Public and Private RSA Keys, given bit size of primes (default 2048)
"""
function gen_RSAKeys(num_bits::Integer = 2048)::Tuple{PublicRSAKey, PrivateRSAKey}
  size_offset = secure_rand(1, 5)

  # generate large primes p and q
  # make p and q differ by a few bits to make factoring harder
  p = gen_prime(num_bits)
  q = gen_prime(num_bits + size_offset)

  n = p*q # RSAKey modulus
  λ_n = lcm(p - 1, q - 1) # Carmichael's totient function for n

  # compute e (public encryption key)
  e = secure_rand(2^16 + 1, λ_n)
  while gcd(e, λ_n) != 1
    e = secure_rand(1, λ_n)
  end

  # compute d (private decryption key)
  # such that d*e ≡ 1 mod λ_n
  d = invmod(e, λ_n)

  public_key = PublicRSAKey(e, n)
  private_key = PrivateRSAKey(d, n)

  return public_key, private_key
end

"""
  `gen_salt()::Bytes`

Generates random 8-bit salt
"""
function gen_salt()::Bytes
  hex_val = hexdigest("sha256", secure_randstring(16))[1:16]
  return hex2bytes(hex_val)
end

"""
  `gen_key([passphrase::String])`

Generates 32 bit key, (can be influenced by a given passphrase, optional)
"""
function gen_key32()::Bytes
  passphrase = secure_randstring(16)
  salt = gen_salt()
  # generate key and initialization vector using passphrase and salt
  (key32, iv16) = gen_key32_iv16(Vector{UInt8}(passphrase), salt)
  return key32
end

function gen_key32(passphrase::String)::Bytes
  salt = gen_salt()
  # generate key and initialization vector using passphrase and salt
  (key32, iv16) = gen_key32_iv16(Vector{UInt8}(passphrase), salt)
  return key32
end

"""
  `to_str(bytes::Bytes)::String`

Converts binary to ascii string
"""
function to_str(bytes::Bytes)::String
  return join(Char(i) for i in bytes)
end

"""
  `to_bytes(str::String)::Bytes`

Converts ascii string to bytes (Vector{UInt8})
"""
function to_bytes(str::String)::Bytes
  return Bytes(str)
end


text:
import NMF.nndsvd

function init_nndsvd!(glrm::GLRM; scale::Bool=true, zeroh::Bool=false,
                      variant::Symbol=:std, max_iters::Int=0)
    # NNDSVD initialization:
    #    Boutsidis C, Gallopoulos E (2007). SVD based initialization: A head
    #    start for nonnegative matrix factorization. Pattern Recognition
    m,n = size(glrm.A)

    # only initialize based on observed entries
    A_init = zeros(m,n)
    for i = 1:n
        A_init[glrm.observed_examples[i],i] = glrm.A[glrm.observed_examples[i],i]
    end

    # scale all columns by the Loss.scale parameter
    if scale
        for i = 1:n
            A_init[:,i] .*= glrm.losses[i].scale
        end
    end

    # run the first nndsvd initialization
    W,H = nndsvd(A_init, glrm.k, zeroh=zeroh, variant=variant)
    glrm.X = W'
    glrm.Y = H

    # If max_iters>0 do a soft impute for the missing entries of A.
    #   Iterate: Estimate missing entries of A with W*H
    #            Update (W,H) nndsvd estimate based on new A
    for iter = 1:max_iters
        # Update missing entries of A_init
        for j = 1:n
            for i = setdiff(1:m,glrm.observed_examples[j])
                A_init[i,j] = dot(glrm.X[:,i],glrm.Y[:,j])
            end
        end
        # Re-estimate W and H
        W,H = nndsvd(A_init, glrm.k, zeroh=zeroh, variant=variant)
        glrm.X = W'
        glrm.Y = H
    end
end