`llvmcall` error "llvmcall only supports intrinsic calls" for scalar integer intrinsics on Apple M2

Hello all!

I’m encountering a very strange issue using llvmcall on my Apple M2 Mac with Julia 1.11.3. Hoping someone might have some insights or suggestions.

Problem:

When I try to use llvmcall to call basic LLVM scalar integer intrinsics (like llvm.abs.i64, llvm.neg.i64, llvm.add.i64, etc.), I consistently get the error: "ERROR: llvmcall only supports intrinsic calls". This is happening even when I believe I am correctly calling intrinsics with the right types (using Int64 or VecElement{Int64}, etc.).

However, llvmcall seems to work perfectly fine for floating-point intrinsics (both scalar like llvm.pow.f64 and vector like llvm.fmuladd.v2f64).

Example Code (Failing - Integer Intrinsics):

julia> abs_i64(x) = ccall("llvm.abs.i64", llvmcall, Int64, (Int64,), x)
abs_i64 (generic function with 1 method)

julia> abs_i64(-5)
ERROR: llvmcall only supports intrinsic calls
Stacktrace:
 [1] abs_i64(x::Int64)
   @ Main ./REPL[1]:1
 [2] top-level scope
   @ REPL[2]:1

julia> neg_i64(x::Int64) = ccall("llvm.neg.i64", llvmcall, Int64, (Int64,), x)
neg_i64 (generic function with 1 method)

julia> neg_i64(5)
ERROR: llvmcall only supports intrinsic calls
Stacktrace:
 [1] neg_i64(x::Int64)
   @ Main ./REPL[3]:1
 [2] top-level scope
   @ REPL[4]:1

# ... (and similarly for add_i64, sub_i64, mul_i64, sdiv_i64, srem_i64, pmull64) ...

Example Code (Working - Floating-Point Intrinsics):

julia> llvm_pow(a, b) = ccall("llvm.pow.f64", llvmcall, Float64, (Float64, Float64), a, b)
llvm_pow (generic function with 1 method)

julia> llvm_pow(2.0, 3.0)
8.0

julia> fmuladd_v2f64(a, b, c) = ccall("llvm.fmuladd.v2f64", llvmcall, NTuple{2, VecElement{Float64}}, (NTuple{2, VecElement{Float64}}, NTuple{2, VecElement{Float64}}, NTuple{2, VecElement{Float64}}), a, b, c)
fmuladd_v2f64 (generic function with 1 method)

julia> a_vec = (VecElement(1.0), VecElement(1.0))
julia> fmuladd_v2f64(a_vec, a_vec, a_vec)
(VecElement{Float64}(2.0), VecElement{Float64}(2.0))

Observations with @code_llvm:

When I examine the generated LLVM IR using @code_llvm for a failing integer intrinsic, it appears that Julia is not even attempting to call the intrinsic. Instead, it’s directly generating code to throw the error. For example:

julia> @code_llvm abs_i64(-5)
; Function Signature: abs_i64(Int64)
;  @ REPL[1]:1 within `abs_i64`
define i64 @julia_abs_i64_9092(i64 signext %"x::Int64") #0 {
top:
  call void @ijl_error(ptr nonnull @"_j_str_llvmcall only supports in...#1")
  unreachable
}

This suggests the issue is not with the LLVM code generation itself, but rather with how Julia is resolving or recognizing scalar integer intrinsics within llvmcall on my system.

Environment Information:

julia> versioninfo()
Julia Version 1.11.3
Commit d63adeda50d (2025-01-21 19:42 UTC)
Build Info:
  Official https://julialang.org/ release
Platform Info:
  OS: macOS (arm64-apple-darwin24.0.0)
  CPU: 8 × Apple M2
  WORD_SIZE: 64
  LLVM: libLLVM-16.0.6 (ORCJIT, apple-m2)
Threads: 1 default, 0 interactive, 1 GC (on 4 virtual cores)

Questions:

  • Is this a known issue with llvmcall on Apple M2 or ARM64?
  • Is there something I’m missing in how I’m defining or calling these integer intrinsics?
  • Are there any workarounds or alternative approaches to achieve the same functionality (calling LLVM integer intrinsics)?
  • Any guidance on how to further debug this or report it as a potential bug would be awesome!

Thank you all :slight_smile:

The issue here is that your instrinsics signature does not match LLVMs LLVM Language Reference Manual — LLVM 20.0.0git documentation
As you can see
declare i32 @llvm.abs.i32(i32 <src>, i1 <is_int_min_poison>)
it takes two arguments instead of just one, so to call it you must do a similar thing.

julia> abs_i64(x) = ccall("llvm.abs.i64", llvmcall, Int64, (Int64, Bool), x, true)
abs_i64 (generic function with 1 method)

julia> abs_i64(-1)
1

julia> @code_llvm abs_i64(-1)
;  @ REPL[1]:1 within `abs_i64`
define i64 @julia_abs_i64_125(i64 signext %0) #0 {
top:
  %1 = call i64 @llvm.abs.i64(i64 %0, i1 zeroext true)
  ret i64 %1
}

Always check the LangRef before using the intrinsic

2 Likes

ha, you’re totally right, thank you !! Thanks for the ref as well, I couldn’t seem to find a decent aggregation of all of the intrinsics in one page!