When are things like `Py_FatalError` being added to the underlying LLVM symbol table?

Context: I am currently trying to port the LLVM IR generated from numba.cfunc to a pure C environment to run.

I notice that most symbols are added to LLVM symbol table through

def add_symbol(name, address):
    """
    Register the *address* of global symbol *name*.  This will make
    it usable (e.g. callable) from LLVM-compiled functions.
    """
    ffi.lib.LLVMPY_AddSymbol(_encode_string(name), c_void_p(address))

in llvmlite at llvmlite/binding/dylib.py

But I don’t see any statement like add_symbol("Py_FatalError", ...)

Question: When are things like Py_FatalError being added to the underlying LLVM?
In other words, how is it possible that jitted LLVM IR can run without Py_FatalError being explicitly added to the symbol?

Or perhaps that means there exist a way in LLVM to expose the C functions in the global namespace to the jitted LLVM IR?

I am a newbie in LLVM so hope someone can shed some light on the mechanism of this.


Probably not relevant to the question for just for context, the LLVM IR generated looks like this:

; ModuleID = 'my_cfunc'
source_filename = "<string>"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-grtev4-linux-gnu"

@_ZN08NumbaEnv8__main__8my_cfuncB2v1B52c8tJTIeFIjxB2IKSgI4CrvQClUYkACQB1EiFSRSVgFmaAA_3d_3dE8int32_2ai = common local_unnamed_addr global ptr null
@".const.Error creating Python tuple from runtime exception arguments" = internal constant [61 x i8] c"Error creating Python tuple from runtime exception arguments\00"
@".const.<numba.core.cpu.CPUContext object at 0x10d13b5a6a90>" = internal constant [53 x i8] c"<numba.core.cpu.CPUContext object at 0x10d13b5a6a90>\00"
@_ZN08NumbaEnv5numba2np8arrayobj11impl_carray12_3clocals_3e4implB2v2B40c8tJTIeFIjxB2IKSgI4CrvQClcaMQ5hEUQmYpQkAE8int32_2a8UniTupleIiLi1EE27omitted_28default_3dNone_29 = common local_unnamed_addr global ptr null
@_ZN08NumbaEnv5numba2np8arrayobj15_call_allocatorB2v7B42c8tJTC_2fWQA93W1AaAIYBPIqRBFCjDSZRVAJmaQIAEN29typeref_5b_3cclass_20_27numba4core5types8npytypes14Array_27_3e_5dExj = common local_unnamed_addr global ptr null
@_ZN08NumbaEnv5numba2np8arrayobj18_ol_array_allocate12_3clocals_3e4implB2v8B40c8tJTIeFIjxB2IKSgI4CrvQClcaMQ5hEUQmYpQkAEN29typeref_5b_3cclass_20_27numba4core5types8npytypes14Array_27_3e_5dExj = common local_unnamed_addr global ptr null
@.const.pickledata.18490829436608 = internal constant [86 x i8] c"\80\04\95K\00\00\00\00\00\00\00\8C\08builtins\94\8C\0BMemoryError\94\93\94\8C'Allocation failed (probably too large).\94\85\94N\87\94."
@.const.pickledata.18490829436608.sha1 = internal constant [20 x i8] c"\BA(\9D\81\F0\\p \F3G|\15sH\04\DFe\AB\E2\09"
@.const.picklebuf.18490829436608 = internal constant { ptr, i32, ptr, ptr, i32 } { ptr @.const.pickledata.18490829436608, i32 86, ptr @.const.pickledata.18490829436608.sha1, ptr null, i32 0 }
@_ZN08NumbaEnv5numba2np8arrayobj9array_dot12_3clocals_3e8dot_implB2v3B40c8tJTIeFIjxB2IKSgI4CrvQClcaMQ5hEUQmYpQkAE5ArrayIfLi1E1C7mutable7alignedE5ArrayIfLi1E1C7mutable7alignedE = common local_unnamed_addr global ptr null
@_ZN08NumbaEnv5numba2np6linalg10dot_2_impl12_3clocals_3e12_3clambda_3eB2v4B40c8tJTIeFIjxB2IKSgI4CrvQClcaMQ5hEUQmYpQkAE5ArrayIfLi1E1C7mutable7alignedE5ArrayIfLi1E1C7mutable7alignedE = common local_unnamed_addr global ptr null
@".const.BLAS wrapper returned with an error" = internal constant [36 x i8] c"BLAS wrapper returned with an error\00"
@PyExc_RuntimeError = external global i8
@_ZN08NumbaEnv5numba2np6linalg8dot_2_vv12_3clocals_3e10check_argsB2v5B42c8tJTC_2fWQA93W1AaAIYBPIqRBFCjDSZRVAJmaQIAE5ArrayIfLi1E1C7mutable7alignedE5ArrayIfLi1E1C7mutable7alignedE = common local_unnamed_addr global ptr null
@_ZN08NumbaEnv5numba2np6linalg11check_c_int12_3clocals_3e4implB2v6B42c8tJTC_2fWQA93W1AaAIYBPIqRBFCjDSZRVAJmaQIAEx = common local_unnamed_addr global ptr null

define range(i32 0, 2) i32 @_ZN8__main__8my_cfuncB2v1B52c8tJTIeFIjxB2IKSgI4CrvQClUYkACQB1EiFSRSVgFmaAA_3d_3dE8int32_2ai(ptr noalias writeonly captures(none) %retptr, ptr noalias writeonly captures(none) %excinfo, ptr readonly captures(none) %arg.array_ptr, i32 %arg.m) local_unnamed_addr {
B0.endif:
  %.95.i.i = alloca float, align 4
  %.107.i.i = alloca i32, align 4
  %.12.i = sext i32 %arg.m to i64
  %.70 = shl nsw i64 %.12.i, 2
  %.7.i.i = tail call ptr @NRT_MemInfo_alloc_aligned(i64 %.70, i32 32), !noalias !0
  %.8.i.i = icmp eq ptr %.7.i.i, null
  br i1 %.8.i.i, label %B0.endif.endif.if.if, label %B0.endif.endif.endif, !prof !7

common.ret:                                       ; preds = %for.end.endif, %B0.endif.endif.if.if
  %common.ret.op = phi i32 [ 1, %B0.endif.endif.if.if ], [ 0, %for.end.endif ]
  ret i32 %common.ret.op

B0.endif.endif.endif:                             ; preds = %B0.endif
  %.5.i = getelementptr i8, ptr %.7.i.i, i64 24
  %.6.i = load ptr, ptr %.5.i, align 8
  %.13427 = icmp sgt i32 %arg.m, 0
  br i1 %.13427, label %iter.check, label %B0.endif.endif.i.i

iter.check:                                       ; preds = %B0.endif.endif.endif
  %0 = ptrtoint ptr %arg.array_ptr to i64
  %.6.i12 = ptrtoint ptr %.6.i to i64
  %min.iters.check = icmp samesign ult i32 %arg.m, 4
  %1 = sub i64 %.6.i12, %0
  %diff.check = icmp ult i64 %1, 128
  %or.cond = select i1 %min.iters.check, i1 true, i1 %diff.check
  br i1 %or.cond, label %for.body.preheader, label %vector.main.loop.iter.check

for.body.preheader:                               ; preds = %vec.epilog.middle.block, %vec.epilog.iter.check, %iter.check
  %loop.index28.ph = phi i64 [ %n.vec, %vec.epilog.iter.check ], [ 0, %iter.check ], [ %n.vec19, %vec.epilog.middle.block ]
  %2 = sub nsw i64 %.12.i, %loop.index28.ph
  %xtraiter = and i64 %2, 7
  %lcmp.mod.not = icmp eq i64 %xtraiter, 0
  br i1 %lcmp.mod.not, label %for.body.prol.loopexit, label %for.body.prol.preheader

for.body.prol.preheader:                          ; preds = %for.body.preheader
  br label %for.body.prol

for.body.prol:                                    ; preds = %for.body.prol.preheader, %for.body.prol
  %lsr.iv = phi i64 [ %xtraiter, %for.body.prol.preheader ], [ %lsr.iv.next, %for.body.prol ]
  %loop.index28.prol = phi i64 [ %.145.prol, %for.body.prol ], [ %loop.index28.ph, %for.body.prol.preheader ]
  %3 = shl nuw nsw i64 %loop.index28.prol, 2
  %scevgep38 = getelementptr i8, ptr %arg.array_ptr, i64 %3
  %4 = shl nuw nsw i64 %loop.index28.prol, 2
  %scevgep37 = getelementptr i8, ptr %.6.i, i64 %4
  %.142.prol = load i32, ptr %scevgep38, align 4
  %.143.prol = sitofp i32 %.142.prol to float
  store float %.143.prol, ptr %scevgep37, align 4
  %.145.prol = add nuw nsw i64 %loop.index28.prol, 1
  %lsr.iv.next = add nsw i64 %lsr.iv, -1
  %prol.iter.cmp.not = icmp eq i64 %lsr.iv.next, 0
  br i1 %prol.iter.cmp.not, label %for.body.prol.loopexit, label %for.body.prol, !llvm.loop !8

for.body.prol.loopexit:                           ; preds = %for.body.prol, %for.body.preheader
  %loop.index28.unr = phi i64 [ %loop.index28.ph, %for.body.preheader ], [ %.145.prol, %for.body.prol ]
  %5 = sub nsw i64 %loop.index28.ph, %.12.i
  %6 = icmp ugt i64 %5, -8
  br i1 %6, label %B0.endif.endif.i.i, label %for.body.preheader5

for.body.preheader5:                              ; preds = %for.body.prol.loopexit
  br label %for.body

vector.main.loop.iter.check:                      ; preds = %iter.check
  %min.iters.check14 = icmp samesign ult i32 %arg.m, 32
  br i1 %min.iters.check14, label %vec.epilog.ph, label %vector.ph

vector.ph:                                        ; preds = %vector.main.loop.iter.check
  %n.vec = and i64 %.12.i, 2147483616
  %7 = lshr i64 %.12.i, 5
  %8 = trunc i64 %7 to i26
  %9 = zext i26 %8 to i64
  %10 = shl nuw nsw i64 %9, 7
  br label %vector.body

vector.body:                                      ; preds = %vector.body, %vector.ph
  %lsr.iv41 = phi i64 [ %lsr.iv.next42, %vector.body ], [ 0, %vector.ph ]
  %sunkaddr = getelementptr i8, ptr %arg.array_ptr, i64 %lsr.iv41
  %wide.load = load <8 x i32>, ptr %sunkaddr, align 4
  %sunkaddr58 = getelementptr i8, ptr %arg.array_ptr, i64 %lsr.iv41
  %sunkaddr59 = getelementptr i8, ptr %sunkaddr58, i64 32
  %wide.load15 = load <8 x i32>, ptr %sunkaddr59, align 4
  %sunkaddr60 = getelementptr i8, ptr %arg.array_ptr, i64 %lsr.iv41
  %sunkaddr61 = getelementptr i8, ptr %sunkaddr60, i64 64
  %wide.load16 = load <8 x i32>, ptr %sunkaddr61, align 4
  %sunkaddr62 = getelementptr i8, ptr %arg.array_ptr, i64 %lsr.iv41
  %sunkaddr63 = getelementptr i8, ptr %sunkaddr62, i64 96
  %wide.load17 = load <8 x i32>, ptr %sunkaddr63, align 4
  %11 = sitofp <8 x i32> %wide.load to <8 x float>
  %12 = sitofp <8 x i32> %wide.load15 to <8 x float>
  %13 = sitofp <8 x i32> %wide.load16 to <8 x float>
  %14 = sitofp <8 x i32> %wide.load17 to <8 x float>
  %sunkaddr64 = getelementptr i8, ptr %.6.i, i64 %lsr.iv41
  store <8 x float> %11, ptr %sunkaddr64, align 4
  %sunkaddr65 = getelementptr i8, ptr %.6.i, i64 %lsr.iv41
  %sunkaddr66 = getelementptr i8, ptr %sunkaddr65, i64 32
  store <8 x float> %12, ptr %sunkaddr66, align 4
  %sunkaddr67 = getelementptr i8, ptr %.6.i, i64 %lsr.iv41
  %sunkaddr68 = getelementptr i8, ptr %sunkaddr67, i64 64
  store <8 x float> %13, ptr %sunkaddr68, align 4
  %sunkaddr69 = getelementptr i8, ptr %.6.i, i64 %lsr.iv41
  %sunkaddr70 = getelementptr i8, ptr %sunkaddr69, i64 96
  store <8 x float> %14, ptr %sunkaddr70, align 4
  %lsr.iv.next42 = add nuw nsw i64 %lsr.iv41, 128
  %15 = icmp eq i64 %10, %lsr.iv.next42
  br i1 %15, label %middle.block, label %vector.body, !llvm.loop !10

middle.block:                                     ; preds = %vector.body
  %cmp.n = icmp eq i64 %n.vec, %.12.i
  br i1 %cmp.n, label %B0.endif.endif.i.i, label %vec.epilog.iter.check

vec.epilog.iter.check:                            ; preds = %middle.block
  %16 = and i32 %arg.m, 28
  %min.epilog.iters.check = icmp eq i32 %16, 0
  br i1 %min.epilog.iters.check, label %for.body.preheader, label %vec.epilog.ph

vec.epilog.ph:                                    ; preds = %vec.epilog.iter.check, %vector.main.loop.iter.check
  %vec.epilog.resume.val = phi i64 [ %n.vec, %vec.epilog.iter.check ], [ 0, %vector.main.loop.iter.check ]
  %n.vec19 = and i64 %.12.i, 2147483644
  br label %vec.epilog.vector.body

vec.epilog.vector.body:                           ; preds = %vec.epilog.vector.body, %vec.epilog.ph
  %index20 = phi i64 [ %vec.epilog.resume.val, %vec.epilog.ph ], [ %index.next22, %vec.epilog.vector.body ]
  %17 = shl i64 %index20, 2
  %scevgep40 = getelementptr i8, ptr %arg.array_ptr, i64 %17
  %18 = shl i64 %index20, 2
  %scevgep39 = getelementptr i8, ptr %.6.i, i64 %18
  %wide.load21 = load <4 x i32>, ptr %scevgep40, align 4
  %19 = sitofp <4 x i32> %wide.load21 to <4 x float>
  store <4 x float> %19, ptr %scevgep39, align 4
  %index.next22 = add nuw i64 %index20, 4
  %20 = icmp eq i64 %n.vec19, %index.next22
  br i1 %20, label %vec.epilog.middle.block, label %vec.epilog.vector.body, !llvm.loop !13

vec.epilog.middle.block:                          ; preds = %vec.epilog.vector.body
  %cmp.n23 = icmp eq i64 %n.vec19, %.12.i
  br i1 %cmp.n23, label %B0.endif.endif.i.i, label %for.body.preheader

B0.endif.endif.if.if:                             ; preds = %B0.endif
  store ptr @.const.picklebuf.18490829436608, ptr %excinfo, align 8
  br label %common.ret

for.body:                                         ; preds = %for.body.preheader5, %for.body
  %loop.index28 = phi i64 [ %.145.7, %for.body ], [ %loop.index28.unr, %for.body.preheader5 ]
  %sunkaddr71 = mul i64 %loop.index28, 4
  %sunkaddr72 = getelementptr i8, ptr %arg.array_ptr, i64 %sunkaddr71
  %.142 = load i32, ptr %sunkaddr72, align 4
  %.143 = sitofp i32 %.142 to float
  %sunkaddr73 = mul i64 %loop.index28, 4
  %sunkaddr74 = getelementptr i8, ptr %.6.i, i64 %sunkaddr73
  store float %.143, ptr %sunkaddr74, align 4
  %sunkaddr75 = mul i64 %loop.index28, 4
  %sunkaddr76 = getelementptr i8, ptr %arg.array_ptr, i64 %sunkaddr75
  %sunkaddr77 = getelementptr i8, ptr %sunkaddr76, i64 4
  %.142.1 = load i32, ptr %sunkaddr77, align 4
  %.143.1 = sitofp i32 %.142.1 to float
  %sunkaddr78 = mul i64 %loop.index28, 4
  %sunkaddr79 = getelementptr i8, ptr %.6.i, i64 %sunkaddr78
  %sunkaddr80 = getelementptr i8, ptr %sunkaddr79, i64 4
  store float %.143.1, ptr %sunkaddr80, align 4
  %sunkaddr81 = mul i64 %loop.index28, 4
  %sunkaddr82 = getelementptr i8, ptr %arg.array_ptr, i64 %sunkaddr81
  %sunkaddr83 = getelementptr i8, ptr %sunkaddr82, i64 8
  %.142.2 = load i32, ptr %sunkaddr83, align 4
  %.143.2 = sitofp i32 %.142.2 to float
  %sunkaddr84 = mul i64 %loop.index28, 4
  %sunkaddr85 = getelementptr i8, ptr %.6.i, i64 %sunkaddr84
  %sunkaddr86 = getelementptr i8, ptr %sunkaddr85, i64 8
  store float %.143.2, ptr %sunkaddr86, align 4
  %sunkaddr87 = mul i64 %loop.index28, 4
  %sunkaddr88 = getelementptr i8, ptr %arg.array_ptr, i64 %sunkaddr87
  %sunkaddr89 = getelementptr i8, ptr %sunkaddr88, i64 12
  %.142.3 = load i32, ptr %sunkaddr89, align 4
  %.143.3 = sitofp i32 %.142.3 to float
  %sunkaddr90 = mul i64 %loop.index28, 4
  %sunkaddr91 = getelementptr i8, ptr %.6.i, i64 %sunkaddr90
  %sunkaddr92 = getelementptr i8, ptr %sunkaddr91, i64 12
  store float %.143.3, ptr %sunkaddr92, align 4
  %sunkaddr93 = mul i64 %loop.index28, 4
  %sunkaddr94 = getelementptr i8, ptr %arg.array_ptr, i64 %sunkaddr93
  %sunkaddr95 = getelementptr i8, ptr %sunkaddr94, i64 16
  %.142.4 = load i32, ptr %sunkaddr95, align 4
  %.143.4 = sitofp i32 %.142.4 to float
  %sunkaddr96 = mul i64 %loop.index28, 4
  %sunkaddr97 = getelementptr i8, ptr %.6.i, i64 %sunkaddr96
  %sunkaddr98 = getelementptr i8, ptr %sunkaddr97, i64 16
  store float %.143.4, ptr %sunkaddr98, align 4
  %sunkaddr99 = mul i64 %loop.index28, 4
  %sunkaddr100 = getelementptr i8, ptr %arg.array_ptr, i64 %sunkaddr99
  %sunkaddr101 = getelementptr i8, ptr %sunkaddr100, i64 20
  %.142.5 = load i32, ptr %sunkaddr101, align 4
  %.143.5 = sitofp i32 %.142.5 to float
  %sunkaddr102 = mul i64 %loop.index28, 4
  %sunkaddr103 = getelementptr i8, ptr %.6.i, i64 %sunkaddr102
  %sunkaddr104 = getelementptr i8, ptr %sunkaddr103, i64 20
  store float %.143.5, ptr %sunkaddr104, align 4
  %sunkaddr105 = mul i64 %loop.index28, 4
  %sunkaddr106 = getelementptr i8, ptr %arg.array_ptr, i64 %sunkaddr105
  %sunkaddr107 = getelementptr i8, ptr %sunkaddr106, i64 24
  %.142.6 = load i32, ptr %sunkaddr107, align 4
  %.143.6 = sitofp i32 %.142.6 to float
  %sunkaddr108 = mul i64 %loop.index28, 4
  %sunkaddr109 = getelementptr i8, ptr %.6.i, i64 %sunkaddr108
  %sunkaddr110 = getelementptr i8, ptr %sunkaddr109, i64 24
  store float %.143.6, ptr %sunkaddr110, align 4
  %sunkaddr111 = mul i64 %loop.index28, 4
  %sunkaddr112 = getelementptr i8, ptr %arg.array_ptr, i64 %sunkaddr111
  %sunkaddr113 = getelementptr i8, ptr %sunkaddr112, i64 28
  %.142.7 = load i32, ptr %sunkaddr113, align 4
  %.143.7 = sitofp i32 %.142.7 to float
  %sunkaddr114 = mul i64 %loop.index28, 4
  %sunkaddr115 = getelementptr i8, ptr %.6.i, i64 %sunkaddr114
  %sunkaddr116 = getelementptr i8, ptr %sunkaddr115, i64 28
  store float %.143.7, ptr %sunkaddr116, align 4
  %.145.7 = add nuw nsw i64 %loop.index28, 8
  %exitcond.not.7 = icmp eq i64 %.12.i, %.145.7
  br i1 %exitcond.not.7, label %B0.endif.endif.i.i, label %for.body, !llvm.loop !14

B0.endif.endif.i.i:                               ; preds = %for.body, %vec.epilog.middle.block, %middle.block, %for.body.prol.loopexit, %B0.endif.endif.endif
  tail call void @NRT_incref(ptr nonnull %.7.i.i)
  tail call void @NRT_decref(ptr null)
  call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %.95.i.i), !noalias !15
  call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %.107.i.i), !noalias !15
  store float 0.000000e+00, ptr %.95.i.i, align 4, !noalias !19
  store i32 0, ptr %.107.i.i, align 4, !noalias !19
  %.104.i.i = call i32 @numba_xxdot(i8 115, i8 0, i64 %.12.i, ptr %.6.i, ptr %.6.i, ptr nonnull %.95.i.i), !noalias !19
  %.105.not.i.i = icmp eq i32 %.104.i.i, 0
  br i1 %.105.not.i.i, label %for.end.endif, label %B0.endif.endif.if.i.i, !prof !23

B0.endif.endif.if.i.i:                            ; preds = %B0.endif.endif.i.i
  call void @numba_gil_ensure(ptr nonnull %.107.i.i), !noalias !19
  call void @Py_FatalError(ptr nonnull @".const.BLAS wrapper returned with an error"), !noalias !19
  unreachable

for.end.endif:                                    ; preds = %B0.endif.endif.i.i
  %.112.i.i = load float, ptr %.95.i.i, align 4, !noalias !19
  call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %.95.i.i), !noalias !15
  call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %.107.i.i), !noalias !15
  tail call void @NRT_decref(ptr nonnull %.7.i.i)
  tail call void @NRT_decref(ptr nonnull %.7.i.i)
  %.188 = fptosi float %.112.i.i to i32
  store i32 %.188, ptr %retptr, align 4
  br label %common.ret
}

define i32 @cfunc._ZN8__main__8my_cfuncB2v1B52c8tJTIeFIjxB2IKSgI4CrvQClUYkACQB1EiFSRSVgFmaAA_3d_3dE8int32_2ai(ptr readonly captures(none) %.1, i32 %.2) local_unnamed_addr {
entry:
  %.4 = alloca i32, align 4
  store i32 0, ptr %.4, align 4
  %excinfo = alloca ptr, align 8
  store ptr null, ptr %excinfo, align 8
  %.8 = call i32 @_ZN8__main__8my_cfuncB2v1B52c8tJTIeFIjxB2IKSgI4CrvQClUYkACQB1EiFSRSVgFmaAA_3d_3dE8int32_2ai(ptr nonnull %.4, ptr nonnull %excinfo, ptr %.1, i32 %.2) #3
  %.9 = load ptr, ptr %excinfo, align 8
  %.18 = load i32, ptr %.4, align 4
  %.20 = alloca i32, align 4
  store i32 0, ptr %.20, align 4
  %cond = icmp eq i32 %.8, 0
  br i1 %cond, label %common.ret, label %entry.if.if

common.ret:                                       ; preds = %entry.if.if.if.if, %.23, %entry
  %common.ret.op = phi i32 [ 0, %entry.if.if.if.if ], [ %.18, %.23 ], [ %.18, %entry ]
  ret i32 %common.ret.op

.23:                                              ; preds = %entry.if.if.endif.if, %entry.if.if.endif
  %.71 = call ptr @PyUnicode_FromString(ptr nonnull @".const.<numba.core.cpu.CPUContext object at 0x10d13b5a6a90>")
  call void @PyErr_WriteUnraisable(ptr %.71)
  call void @Py_DecRef(ptr %.71)
  call void @numba_gil_release(ptr nonnull %.20)
  br label %common.ret

entry.if.if:                                      ; preds = %entry
  call void @numba_gil_ensure(ptr nonnull %.20)
  call void @PyErr_Clear()
  %.26 = load { ptr, i32, ptr, ptr, i32 }, ptr %.9, align 8
  %.27 = extractvalue { ptr, i32, ptr, ptr, i32 } %.26, 4
  %.28 = icmp sgt i32 %.27, 0
  %.31 = extractvalue { ptr, i32, ptr, ptr, i32 } %.26, 0
  %.33 = extractvalue { ptr, i32, ptr, ptr, i32 } %.26, 1
  br i1 %.28, label %entry.if.if.if, label %entry.if.if.else

entry.if.if.if:                                   ; preds = %entry.if.if
  %.34 = sext i32 %.33 to i64
  %.35 = call ptr @PyBytes_FromStringAndSize(ptr %.31, i64 %.34)
  %.36 = load { ptr, i32, ptr, ptr, i32 }, ptr %.9, align 8
  %.37 = extractvalue { ptr, i32, ptr, ptr, i32 } %.36, 2
  %.39 = extractvalue { ptr, i32, ptr, ptr, i32 } %.36, 3
  %.41 = call ptr %.39(ptr %.37)
  %.42 = icmp eq ptr %.41, null
  br i1 %.42, label %entry.if.if.if.if, label %entry.if.if.if.endif, !prof !7

entry.if.if.else:                                 ; preds = %entry.if.if
  %.55 = extractvalue { ptr, i32, ptr, ptr, i32 } %.26, 2
  %.56 = call ptr @numba_unpickle(ptr %.31, i32 %.33, ptr %.55)
  br label %entry.if.if.endif

entry.if.if.endif:                                ; preds = %entry.if.if.if.endif, %entry.if.if.else
  %.58 = phi ptr [ %.46, %entry.if.if.if.endif ], [ %.56, %entry.if.if.else ]
  %.59.not = icmp eq ptr %.58, null
  br i1 %.59.not, label %.23, label %entry.if.if.endif.if, !prof !7

entry.if.if.if.if:                                ; preds = %entry.if.if.if
  call void @PyErr_SetString(ptr nonnull @PyExc_RuntimeError, ptr nonnull @".const.Error creating Python tuple from runtime exception arguments")
  br label %common.ret

entry.if.if.if.endif:                             ; preds = %entry.if.if.if
  %.46 = call ptr @numba_runtime_build_excinfo_struct(ptr %.35, ptr nonnull %.41)
  call void @NRT_Free(ptr nonnull %.9)
  br label %entry.if.if.endif

entry.if.if.endif.if:                             ; preds = %entry.if.if.endif
  call void @numba_do_raise(ptr nonnull %.58)
  br label %.23
}

declare void @numba_gil_ensure(ptr) local_unnamed_addr

declare ptr @PyUnicode_FromString(ptr) local_unnamed_addr

declare void @PyErr_WriteUnraisable(ptr) local_unnamed_addr

declare void @Py_DecRef(ptr) local_unnamed_addr

declare void @numba_gil_release(ptr) local_unnamed_addr

declare void @PyErr_Clear() local_unnamed_addr

declare ptr @PyBytes_FromStringAndSize(ptr, i64) local_unnamed_addr

declare ptr @numba_unpickle(ptr, i32, ptr) local_unnamed_addr

declare void @PyErr_SetString(ptr, ptr) local_unnamed_addr

declare ptr @numba_runtime_build_excinfo_struct(ptr, ptr) local_unnamed_addr

declare void @NRT_Free(ptr) local_unnamed_addr

declare void @numba_do_raise(ptr) local_unnamed_addr

declare noalias ptr @NRT_MemInfo_alloc_aligned(i64, i32) local_unnamed_addr

; Function Attrs: mustprogress nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.start.p0(i64 immarg, ptr captures(none)) #0

declare i32 @numba_xxdot(i8, i8, i64, ptr, ptr, ptr) local_unnamed_addr

; Function Attrs: noreturn
declare void @Py_FatalError(ptr) local_unnamed_addr #1

; Function Attrs: mustprogress nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.end.p0(i64 immarg, ptr captures(none)) #0

; Function Attrs: mustprogress nofree noinline norecurse nounwind willreturn memory(argmem: readwrite)
define linkonce_odr void @NRT_incref(ptr %.1) local_unnamed_addr #2 {
.3:
  %.4 = icmp eq ptr %.1, null
  br i1 %.4, label %common.ret, label %.3.endif, !prof !7

common.ret:                                       ; preds = %.3.endif, %.3
  ret void

.3.endif:                                         ; preds = %.3
  %.4.i = atomicrmw add ptr %.1, i64 1 monotonic, align 8
  br label %common.ret
}

; Function Attrs: noinline
define linkonce_odr void @NRT_decref(ptr %.1) local_unnamed_addr #3 {
.3:
  %.4 = icmp eq ptr %.1, null
  br i1 %.4, label %common.ret1, label %.3.endif, !prof !7

common.ret1:                                      ; preds = %.3.endif, %.3
  ret void

.3.endif:                                         ; preds = %.3
  fence release
  %0 = tail call i8 @llvm.x86.atomic.sub.cc.i64(ptr nonnull %.1, i64 1, i32 4)
  %1 = trunc i8 %0 to i1
  br i1 %1, label %.3.endif.if, label %common.ret1, !prof !7

.3.endif.if:                                      ; preds = %.3.endif
  fence acquire
  tail call void @NRT_MemInfo_call_dtor(ptr nonnull %.1)
  ret void
}

; Function Attrs: nounwind
declare i8 @llvm.x86.atomic.sub.cc.i64(ptr, i64, i32 immarg) #4

declare void @NRT_MemInfo_call_dtor(ptr) local_unnamed_addr

attributes #0 = { mustprogress nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
attributes #1 = { noreturn }
attributes #2 = { mustprogress nofree noinline norecurse nounwind willreturn memory(argmem: readwrite) }
attributes #3 = { noinline }
attributes #4 = { nounwind }

!0 = !{!1, !3, !4, !6}
!1 = distinct !{!1, !2, !"_ZN5numba2np8arrayobj18_ol_array_allocate12_3clocals_3e4implB2v8B40c8tJTIeFIjxB2IKSgI4CrvQClcaMQ5hEUQmYpQkAEN29typeref_5b_3cclass_20_27numba4core5types8npytypes14Array_27_3e_5dExj: %retptr"}
!2 = distinct !{!2, !"_ZN5numba2np8arrayobj18_ol_array_allocate12_3clocals_3e4implB2v8B40c8tJTIeFIjxB2IKSgI4CrvQClcaMQ5hEUQmYpQkAEN29typeref_5b_3cclass_20_27numba4core5types8npytypes14Array_27_3e_5dExj"}
!3 = distinct !{!3, !2, !"_ZN5numba2np8arrayobj18_ol_array_allocate12_3clocals_3e4implB2v8B40c8tJTIeFIjxB2IKSgI4CrvQClcaMQ5hEUQmYpQkAEN29typeref_5b_3cclass_20_27numba4core5types8npytypes14Array_27_3e_5dExj: %excinfo"}
!4 = distinct !{!4, !5, !"_ZN5numba2np8arrayobj15_call_allocatorB2v7B42c8tJTC_2fWQA93W1AaAIYBPIqRBFCjDSZRVAJmaQIAEN29typeref_5b_3cclass_20_27numba4core5types8npytypes14Array_27_3e_5dExj: %retptr"}
!5 = distinct !{!5, !"_ZN5numba2np8arrayobj15_call_allocatorB2v7B42c8tJTC_2fWQA93W1AaAIYBPIqRBFCjDSZRVAJmaQIAEN29typeref_5b_3cclass_20_27numba4core5types8npytypes14Array_27_3e_5dExj"}
!6 = distinct !{!6, !5, !"_ZN5numba2np8arrayobj15_call_allocatorB2v7B42c8tJTC_2fWQA93W1AaAIYBPIqRBFCjDSZRVAJmaQIAEN29typeref_5b_3cclass_20_27numba4core5types8npytypes14Array_27_3e_5dExj: %excinfo"}
!7 = !{!"branch_weights", i32 1, i32 99}
!8 = distinct !{!8, !9}
!9 = !{!"llvm.loop.unroll.disable"}
!10 = distinct !{!10, !11, !12}
!11 = !{!"llvm.loop.isvectorized", i32 1}
!12 = !{!"llvm.loop.unroll.runtime.disable"}
!13 = distinct !{!13, !11, !12}
!14 = distinct !{!14, !11}
!15 = !{!16, !18}
!16 = distinct !{!16, !17, !"_ZN5numba2np8arrayobj9array_dot12_3clocals_3e8dot_implB2v3B40c8tJTIeFIjxB2IKSgI4CrvQClcaMQ5hEUQmYpQkAE5ArrayIfLi1E1C7mutable7alignedE5ArrayIfLi1E1C7mutable7alignedE: %retptr"}
!17 = distinct !{!17, !"_ZN5numba2np8arrayobj9array_dot12_3clocals_3e8dot_implB2v3B40c8tJTIeFIjxB2IKSgI4CrvQClcaMQ5hEUQmYpQkAE5ArrayIfLi1E1C7mutable7alignedE5ArrayIfLi1E1C7mutable7alignedE"}
!18 = distinct !{!18, !17, !"_ZN5numba2np8arrayobj9array_dot12_3clocals_3e8dot_implB2v3B40c8tJTIeFIjxB2IKSgI4CrvQClcaMQ5hEUQmYpQkAE5ArrayIfLi1E1C7mutable7alignedE5ArrayIfLi1E1C7mutable7alignedE: %excinfo"}
!19 = !{!20, !22, !16, !18}
!20 = distinct !{!20, !21, !"_ZN5numba2np6linalg10dot_2_impl12_3clocals_3e12_3clambda_3eB2v4B40c8tJTIeFIjxB2IKSgI4CrvQClcaMQ5hEUQmYpQkAE5ArrayIfLi1E1C7mutable7alignedE5ArrayIfLi1E1C7mutable7alignedE: %retptr"}
!21 = distinct !{!21, !"_ZN5numba2np6linalg10dot_2_impl12_3clocals_3e12_3clambda_3eB2v4B40c8tJTIeFIjxB2IKSgI4CrvQClcaMQ5hEUQmYpQkAE5ArrayIfLi1E1C7mutable7alignedE5ArrayIfLi1E1C7mutable7alignedE"}
!22 = distinct !{!22, !21, !"_ZN5numba2np6linalg10dot_2_impl12_3clocals_3e12_3clambda_3eB2v4B40c8tJTIeFIjxB2IKSgI4CrvQClcaMQ5hEUQmYpQkAE5ArrayIfLi1E1C7mutable7alignedE5ArrayIfLi1E1C7mutable7alignedE: %excinfo"}
!23 = !{!"branch_weights", i32 99, i32 1}

generated from:

@nb.cfunc(
    nb.types.intc(nb.types.CPointer(nb.types.intc), nb.types.intc),
    nopython=True,
)
def my_cfunc(array_ptr, m):
  a = nb.carray(array_ptr, (m,))
  a = a.astype(np.float32)
  return a.dot(a)

These guys declare void @PyErr_WriteUnraisable(ptr) local_unnamed_addr etc. are resolved at runtime and are linked dynamically.

For your purposes, you can compile your LLVM code and also (dynamically) link it against Python shared lib on your machine (provided you decide to keep the entire LLVM code produced by numba verbatim), then you want to declare the @cfunc._Z... one in your C code, and so on.

Let me know if you need help with the specifics.

Thanks Milton!

Got a few more questions:

  1. Can you point me out where/when does Numba link these PyErr_WriteUnraisable things in Python in Numba source code? Or are these things automatically linked?

So far I don’t think I see any load_library_permanently(filename) being called on Python shared library in Numba source code. I am still curious when do these Py* things get linked. Like when does LLVM know that it can use Python symbols in the shared library.

I even tried to intercept llvm::sys::DynamicLibrary::LoadLibraryPermanently by fprintf(stderr) in C but I don’t see Python shared library being called on that.

  1. Can you add more details how to dynamically link Python shared library with LLVM in C?

Thanks!

I think I’ve found the place :slightly_smiling_face:

#ifndef PyAPI_FUNC
#       define PyAPI_FUNC(RTYPE) Py_EXPORTED_SYMBOL RTYPE
#endif

This in CPython seems to do the symbol exporting job iiuc.

And the takeaway is: the symbol @<symbol> in LLVM IR can refer to any C functions in that has __attribute__ ((visibility ("default"))) in front basically.