# Calling AVX-512 intrinsics from Julia

**URL:** <https://discourse.julialang.org/t/calling-avx-512-intrinsics-from-julia/101079>\
**Category:** General Usage\
**Tags:** bit-twiddling\
**Created:** [July 2, 2023, 12:47pm UTC](https://discourse.julialang.org/t/calling-avx-512-intrinsics-from-julia/101079 "2023-07-02T12:47:19Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![giacomogiudice](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giacomogiudice/32/46050_2.png) [@giacomogiudice](https://discourse.julialang.org/u/giacomogiudice)\
**Post date:** [July 2, 2023, 12:47pm UTC](https://discourse.julialang.org/t/calling-avx-512-intrinsics-from-julia/101079/1 "2023-07-02T12:47:20Z")

</div>

I am having trouble calling some AVX-512 intrinsics from Julia, coming directly from [this post](https://lemire.me/blog/2023/06/29/dynamic-bit-shuffle-using-avx-512/).

The example on the blog post compiles and runs fine on the CPU, since it has the `avx512_bitalg` CPU flag.  
The problematic instruction in question is `_mm512_bitshuffle_epi64_mask`. Using [godbolt](https://godbolt.org/), I extract the corresponding LLVM name from the line

```julia
  %7 = tail call <64 x i1> @llvm.x86.avx512.vpshufbitqmb.512(<64 x i8> %6, <64 x i8> %4), !dbg !377

```

but the following fails:

```julia
__m512i = NTuple{64, VecElement{Int8}}

x = __m512i(ntuple(_ -> rand(Int8), 64))
p = __m512i(ntuple(_ -> rand(Int8), 64))
ccall("llvm.x86.avx512.vpshufbitqmb.512", llvmcall, Int64, ( __m512i,__ m512i), x, p)

```

with `ERROR: llvmcall only supports intrinsic calls`.  
Notice that the intrinsic returns a `<64 x i1>`, and I am hoping there is some casting to `Int64` happening implicitly.

Trying to write it down explicitly as

```julia
using SIMD

function _test(x, p)
    __m512i = SIMD.LVec{64, Int8}

    return Base.llvmcall("""
        %3 = call <64 x i1> @llvm.x86.avx512.vpshufbitqmb.512(<64 x i8> %0, <64 x i8> %1)
        %4 = bitcast <64 x i1> %3 to i64
         ret i64 %4
    """, Int64, Tuple{ __m512i,__ m512i}, x, p)
end

```

also fails with a different error

```julia
ERROR: Failed to parse LLVM assembly:
<string>:3:21: error: use of undefined value '@llvm.x86.avx512.vpshufbitqmb.512'
%3 = call <64 x i1> @llvm.x86.avx512.vpshufbitqmb.512(<64 x i8> %0, <64 x i8> %1)
                    ^

```

It seems that the intrinsic is not recognized by LLVM. **Is there a way to check if the intrinsic is available?**

I am running Julia `1.9.0` with ` LLVM: libLLVM-14.0.6 (ORCJIT, icelake-server)`.

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [July 2, 2023, 1:40pm UTC](https://discourse.julialang.org/t/calling-avx-512-intrinsics-from-julia/101079/2 "2023-07-02T13:40:57Z")

</div>

Here is a [C++ function calling the intrinsic via Godbolt with emit llvm](https://godbolt.org/#g:!((g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:14,fontUsePx:'0',j:1,lang:c%2B%2B,selection:(endColumn:1,endLineNumber:9,positionColumn:1,positionLineNumber:9,selectionStartColumn:1,selectionStartLineNumber:9,startColumn:1,startLineNumber:9),source:'%23include+%3Ccstdint%3E%0A%23include+%3Cimmintrin.h%3E%0A%0Auint64_t+bit_shuffle(uint64_t+w,+uint8_t+indexes%5B64%5D)+%7B%0A++__m512i+as_vec_register+%3D+_mm512_set1_epi64(w)%3B%0A++__mmask64+as_mask+%3D+_mm512_bitshuffle_epi64_mask(as_vec_register,+_mm512_loadu_si512(indexes))%3B%0A++return+_cvtmask64_u64(as_mask)%3B%0A%7D%0A'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0'),(h:compiler,i:(compiler:clang1500,deviceViewOpen:'1',filters:(b:'0',binary:'1',binaryObject:'1',commentOnly:'0',debugCalls:'1',demangle:'0',directives:'0',execute:'1',intel:'0',libraryCode:'0',trim:'1'),flagsViewOpen:'1',fontScale:14,fontUsePx:'0',j:1,lang:c%2B%2B,libs:!(),options:'-march%3Dicelake-server+-O3+-emit-llvm',overrides:!(),selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1),l:'5',n:'0',o:'+x86-64+clang+15.0.0+(Editor+%231)',t:'0')),k:100,l:'4',m:100,n:'0',o:'',s:0,t:'0')),k:100,l:'3',m:100,n:'0',o:'',t:'0')),l:'3',n:'0',o:'',t:'0')),version:4).

Here us the generated IR.

```llvm
; Function Attrs: argmemonly mustprogress nofree nosync nounwind readonly willreturn uwtable
define dso_local noundef i64 @bit_shuffle(unsigned long, unsigned char*)(i64 noundef %0, ptr nocapture noundef readonly %1) local_unnamed_addr #0 !dbg !358 {
  call void @llvm.dbg.value(metadata i64 %0, metadata !364, metadata !DIExpression()), !dbg !368
  call void @llvm.dbg.value(metadata ptr %1, metadata !365, metadata !DIExpression()), !dbg !368
  %3 = insertelement <8 x i64> undef, i64 %0, i64 0, !dbg !369
  call void @llvm.dbg.value(metadata <8 x i64> undef, metadata !366, metadata !DIExpression()), !dbg !368
  %4 = load <64 x i8>, ptr %1, align 1, !dbg !370
  %5 = bitcast <8 x i64> %3 to <64 x i8>, !dbg !374
  %6 = shufflevector <64 x i8> %5, <64 x i8> poison, <64 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7>, !dbg !374
  %7 = tail call <64 x i1> @llvm.x86.avx512.vpshufbitqmb.512(<64 x i8> %6, <64 x i8> %4), !dbg !374
  %8 = bitcast <64 x i1> %7 to i64, !dbg !374
  call void @llvm.dbg.value(metadata i64 %8, metadata !367, metadata !DIExpression()), !dbg !368
  ret i64 %8, !dbg !375
}

; Function Attrs: nofree nosync nounwind readnone
declare <64 x i1> @llvm.x86.avx512.vpshufbitqmb.512(<64 x i8>, <64 x i8>) #1

; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn
declare void @llvm.dbg.value(metadata, metadata, metadata) #2

attributes #0 = { argmemonly mustprogress nofree nosync nounwind readonly willreturn uwtable "frame-pointer"="none" "min-legal-vector-width"="512" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="icelake-server" "target-features"="+adx,+aes,+avx,+avx2,+avx512bitalg,+avx512bw,+avx512cd,+avx512dq,+avx512f,+avx512ifma,+avx512vbmi,+avx512vbmi2,+avx512vl,+avx512vnni,+avx512vpopcntdq,+bmi,+bmi2,+clflushopt,+clwb,+crc32,+cx16,+cx8,+f16c,+fma,+fsgsbase,+fxsr,+gfni,+invpcid,+lzcnt,+mmx,+movbe,+pclmul,+pconfig,+pku,+popcnt,+prfchw,+rdpid,+rdrnd,+rdseed,+sahf,+sgx,+sha,+sse,+sse2,+sse3,+sse4.1,+sse4.2,+ssse3,+vaes,+vpclmulqdq,+wbnoinvd,+x87,+xsave,+xsavec,+xsaveopt,+xsaves" }
attributes #1 = { nofree nosync nounwind readnone }
attributes #2 = { nocallback nofree nosync nounwind readnone speculatable willreturn }

```

Perhaps you are missing the external declaration

```julia
declare <64 x i1> @llvm.x86.avx512.vpshufbitqmb.512(<64 x i8>, <64 x i8>) #1

```

---

<div class="post-metadata">

**Author:** ![giacomogiudice](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giacomogiudice/32/46050_2.png) [@giacomogiudice](https://discourse.julialang.org/u/giacomogiudice)\
**Post date:** [July 3, 2023, 9:34am UTC](https://discourse.julialang.org/t/calling-avx-512-intrinsics-from-julia/101079/3 "2023-07-03T09:34:37Z")

</div>

Thanks for your reply 🙂 .

I know nothing about LLVM IR, but it seems to me that `declare`s should be outside of the function definition.  
I tried

```julia
using SIMD

function _test(x, p)
    __m512i = SIMD.LVec{64, Int8}

    return Base.llvmcall("""
        %3 = call <64 x i1> @llvm.x86.avx512.vpshufbitqmb.512(<64 x i8> %0, <64 x i8> %1)
        %4 = bitcast <64 x i1> %3 to i64
         ret i64 %4
         declare <64 x i1> @llvm.x86.avx512.vpshufbitqmb.512(<64 x i8>, <64 x i8>) #
    """, Int64, Tuple{ __m512i,__ m512i}, x, p)
end

__m512i = NTuple{64, VecElement{Int8}}
x = __m512i(ntuple(_ -> rand(Int8), 64))
p = __m512i(ntuple(_ -> rand(Int8), 64))

_test(x, p)

```

and indeed I get a

```julia
<string>:6:27: error: expected instruction opcode
                          declare <64 x i1> @llvm.x86.avx512.vpshufbitqmb.512(<64 x i8>, <64 x i8>) #

```

It should be possible to call just the intrinsic, as described [here](http://kristofferc.github.io/post/intrinsics/).

For now I am compiling a C code and calling it from julia, but this approach has the disadvantage that the function call does not get inlined.

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [July 3, 2023, 3:33pm UTC](https://discourse.julialang.org/t/calling-avx-512-intrinsics-from-julia/101079/4 "2023-07-03T15:33:54Z")

</div>

The `declare <64 x i1> @llvm.x86.avx512.vpshufbitqmb.512(<64 x i8>, <64 x i8>)` creates a global identifier.

> LLVM identifiers come in two basic types: global and local. Global identifiers (functions, global variables) begin with the `'@'` character.

I’m guessing the LLVM requires the declaration but then references an external implementation somewhere.

Perhaps @kristoffer.carlsson , the author of that blog, would be of more help. @Elrod has more experience with SIMD with packages such as VectorizationBase.jl and LoopVectorization.jl.

> **[GitHub - JuliaSIMD/VectorizationBase.jl: Base library providing...](https://github.com/JuliaSIMD/VectorizationBase.jl)**
>
> Base library providing vectorization-tools (ie, SIMD) that other libraries are built off of. - GitHub - JuliaSIMD/VectorizationBase.jl: Base library providing vectorization-tools (ie, SIMD) that ot...

---

<div class="post-metadata">

**Author:** ![Elrod](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/elrod/32/22461_2.png) [@Elrod](https://discourse.julialang.org/u/Elrod)\
**Post date:** [July 3, 2023, 4:40pm UTC](https://discourse.julialang.org/t/calling-avx-512-intrinsics-from-julia/101079/5 "2023-07-03T16:40:10Z")

</div>

VectorizationBase goes through a function to still support the old llvmcall API, so it is messier than necessary.  
But you can see plenty of examples of it using avx512 specific intrinsics, e.g.:  
[VectorizationBase.jl/src/llvm\_intrin/intrin\_funcs.jl at 9174dcca731144935e438d44ba07f4e4ec3a66c6 · JuliaSIMD/VectorizationBase.jl · GitHub](https://github.com/JuliaSIMD/VectorizationBase.jl/blob/9174dcca731144935e438d44ba07f4e4ec3a66c6/src/llvm_intrin/intrin_funcs.jl#L220)

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [July 3, 2023, 8:17pm UTC](https://discourse.julialang.org/t/calling-avx-512-intrinsics-from-julia/101079/6 "2023-07-03T20:17:19Z")

</div>

I found an icelake-server machine, and I sorted out a few things from your original code.

1. `__m512i` needs to be const when used globally
2. The return type is `<64 x i1>` and not `i64` so we need an explicit `bitcast`.

```julia
julia> import Core.Intrinsics.llvmcall

julia> const __m512i = NTuple{64, VecElement{Int8}}
NTuple{64, VecElement{Int8}}

julia> vpshufbitqmb_512(a,b) = Core.Intrinsics.llvmcall(("""
       declare <64 x i1> @llvm.x86.avx512.vpshufbitqmb.512(<64 x i8>, <64 x i8>)
       define i64 @i64_vpshufbitqmb_512(<64 x i8> %a, <64 x i8> %b) {
         %tmp = call <64 x i1> @llvm.x86.avx512.vpshufbitqmb.512(<64 x i8> %a, <64 x i8> %b)
         %tmp2 = bitcast <64 x i1> %tmp to i64
         ret i64 %tmp2
       }
       ""","i64_vpshufbitqmb_512"), Int64, Tuple{ __m512i,__ m512i}, a, b)
vpshufbitqmb_512 (generic function with 1 method)

julia> x = __m512i(ntuple(_ -> rand(Int8), 64));

julia> p = __m512i(ntuple(_ -> rand(Int8), 64));

julia> vpshufbitqmb_512(x,p)
-68453262247164531

julia> versioninfo()
Julia Version 1.9.1
Commit 147bdf428c (2023-06-07 08:27 UTC)
Platform Info:
  OS: Windows (x86_64-w64-mingw32)
  CPU: 56 × Intel(R) Xeon(R) Gold 6348 CPU @ 2.60GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-14.0.6 (ORCJIT, icelake-server)
  Threads: 1 on 112 virtual cores

julia> Base.BinaryPlatforms.CPUID.test_cpu_feature(Base.BinaryPlatforms.CPUID.JL_X86_avx512bitalg)
true

```

I figured this out by looking at the following examples.

1. [llvm-project/llvm/test/CodeGen/X86/vpshufbitqbm-intrinsics.ll at 147a61618989b6cca1f5f77ed96f930620ff193f · JuliaLang/llvm-project · GitHub](https://github.com/JuliaLang/llvm-project/blob/147a61618989b6cca1f5f77ed96f930620ff193f/llvm/test/CodeGen/X86/vpshufbitqbm-intrinsics.ll#L34C1-L51C74)
2. [VectorizationBase.jl/src/llvm\_intrin/intrin\_funcs.jl at 9174dcca731144935e438d44ba07f4e4ec3a66c6 · JuliaSIMD/VectorizationBase.jl · GitHub](https://github.com/JuliaSIMD/VectorizationBase.jl/blob/9174dcca731144935e438d44ba07f4e4ec3a66c6/src/llvm_intrin/intrin_funcs.jl#L220)

---

<div class="post-metadata">

**Author:** ![giacomogiudice](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giacomogiudice/32/46050_2.png) [@giacomogiudice](https://discourse.julialang.org/u/giacomogiudice)\
**Post date:** [July 4, 2023, 8:45am UTC](https://discourse.julialang.org/t/calling-avx-512-intrinsics-from-julia/101079/7 "2023-07-04T08:45:48Z")

</div>

Wow that’s exactly what I was looking for.  
Thank you so much!
