Skip to main content

rustc_codegen_llvm/builder/
gpu_offload.rs

1use std::ffi::CString;
2
3use bitflags::Flags;
4use llvm::Linkage::*;
5use rustc_abi::Align;
6use rustc_codegen_ssa::MemFlags;
7use rustc_codegen_ssa::common::TypeKind;
8use rustc_codegen_ssa::mir::operand::{OperandRef, OperandValue};
9use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods, BuilderMethods};
10use rustc_middle::bug;
11use rustc_middle::ty::offload_meta::{MappingFlags, OffloadMetadata, OffloadSize};
12
13use crate::builder::Builder;
14use crate::common::CodegenCx;
15use crate::llvm::AttributePlace::Function;
16use crate::llvm::{self, Linkage, Type, Value};
17use crate::{SimpleCx, attributes};
18
19// LLVM kernel-independent globals required for offloading
20pub(crate) struct OffloadGlobals<'ll> {
21    pub launcher_fn: &'ll llvm::Value,
22    pub launcher_ty: &'ll llvm::Type,
23
24    pub kernel_args_ty: &'ll llvm::Type,
25
26    pub offload_entry_ty: &'ll llvm::Type,
27
28    pub begin_mapper: &'ll llvm::Value,
29    pub end_mapper: &'ll llvm::Value,
30    pub mapper_fn_ty: &'ll llvm::Type,
31
32    pub ident_t_global: &'ll llvm::Value,
33}
34
35impl<'ll> OffloadGlobals<'ll> {
36    pub(crate) fn declare(cx: &CodegenCx<'ll, '_>) -> Self {
37        let (launcher_fn, launcher_ty) = generate_launcher(cx);
38        let kernel_args_ty = KernelArgsTy::new_decl(cx);
39        let offload_entry_ty = TgtOffloadEntry::new_decl(cx);
40        let (begin_mapper, _, end_mapper, mapper_fn_ty) = gen_tgt_data_mappers(cx);
41        let ident_t_global = generate_at_one(cx);
42
43        // We want LLVM's openmp-opt pass to pick up and optimize this module, since it covers both
44        // openmp and offload optimizations.
45        llvm::add_module_flag_u32(cx.llmod(), llvm::ModuleFlagMergeBehavior::Max, "openmp", 51);
46
47        OffloadGlobals {
48            launcher_fn,
49            launcher_ty,
50            kernel_args_ty,
51            offload_entry_ty,
52            begin_mapper,
53            end_mapper,
54            mapper_fn_ty,
55            ident_t_global,
56        }
57    }
58}
59
60// We need to register offload before using it. We also should unregister it once we are done, for
61// good measures. Previously we have done so before and after each individual offload intrinsic
62// call, but that comes at a performance cost. The repeated (un)register calls might also confuse
63// the LLVM ompOpt pass, which tries to move operations to a better location. The easiest solution,
64// which we copy from clang, is to just have those two calls once, in the global ctor/dtor section
65// of the final binary.
66pub(crate) fn register_offload<'ll>(cx: &CodegenCx<'ll, '_>) {
67    // First we check quickly whether we already have done our setup, in which case we return early.
68    // Shouldn't be needed for correctness.
69    let register_lib_name = "__tgt_register_lib";
70    if cx.get_function(register_lib_name).is_some() {
71        return;
72    }
73
74    let reg_lib_decl = cx.type_func(&[cx.type_ptr()], cx.type_void());
75    let register_lib = declare_offload_fn(&cx, register_lib_name, reg_lib_decl);
76    let unregister_lib = declare_offload_fn(&cx, "__tgt_unregister_lib", reg_lib_decl);
77
78    let ptr_null = cx.const_null(cx.type_ptr());
79    let const_struct = cx.const_struct(&[cx.get_const_i32(0), ptr_null, ptr_null, ptr_null], false);
80    let omp_descriptor =
81        add_global(cx, ".omp_offloading.descriptor", const_struct, InternalLinkage);
82    // @.omp_offloading.descriptor = internal constant %__tgt_bin_desc { i32 1, ptr @.omp_offloading.device_images, ptr @__start_llvm_offload_entries, ptr @__stop_llvm_offload_entries }
83    // @.omp_offloading.descriptor = internal constant %__tgt_bin_desc { i32 0, ptr null, ptr null, ptr null }
84
85    let atexit = cx.type_func(&[cx.type_ptr()], cx.type_i32());
86    let atexit_fn = declare_offload_fn(cx, "atexit", atexit);
87
88    // FIXME(offload): Drop this, once we fully automated our offload compilation pipeline, since
89    // LLVM will initialize them for us if it sees gpu kernels being registered.
90    let init_ty = cx.type_func(&[], cx.type_void());
91    let init_rtls = declare_offload_fn(cx, "__tgt_init_all_rtls", init_ty);
92
93    let desc_ty = cx.type_func(&[], cx.type_void());
94    let reg_name = ".omp_offloading.descriptor_reg";
95    let unreg_name = ".omp_offloading.descriptor_unreg";
96    let desc_reg_fn = declare_offload_fn(cx, reg_name, desc_ty);
97    let desc_unreg_fn = declare_offload_fn(cx, unreg_name, desc_ty);
98    llvm::set_linkage(desc_reg_fn, InternalLinkage);
99    llvm::set_linkage(desc_unreg_fn, InternalLinkage);
100    llvm::set_section(desc_reg_fn, c".text.startup");
101    llvm::set_section(desc_unreg_fn, c".text.startup");
102
103    // define internal void @.omp_offloading.descriptor_reg() section ".text.startup" {
104    // entry:
105    //   call void @__tgt_register_lib(ptr @.omp_offloading.descriptor)
106    //   call void @__tgt_init_all_rtls()
107    //   %0 = call i32 @atexit(ptr @.omp_offloading.descriptor_unreg)
108    //   ret void
109    // }
110    let bb = Builder::append_block(cx, desc_reg_fn, "entry");
111    let mut a = Builder::build(cx, bb);
112    a.call(reg_lib_decl, None, None, register_lib, &[omp_descriptor], None, None);
113    a.call(init_ty, None, None, init_rtls, &[], None, None);
114    a.call(atexit, None, None, atexit_fn, &[desc_unreg_fn], None, None);
115    a.ret_void();
116
117    // define internal void @.omp_offloading.descriptor_unreg() section ".text.startup" {
118    // entry:
119    //   call void @__tgt_unregister_lib(ptr @.omp_offloading.descriptor)
120    //   ret void
121    // }
122    let bb = Builder::append_block(cx, desc_unreg_fn, "entry");
123    let mut a = Builder::build(cx, bb);
124    a.call(reg_lib_decl, None, None, unregister_lib, &[omp_descriptor], None, None);
125    a.ret_void();
126
127    // @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 101, ptr @.omp_offloading.descriptor_reg, ptr null }]
128    let args = ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [cx.get_const_i32(101), desc_reg_fn, ptr_null]))vec![cx.get_const_i32(101), desc_reg_fn, ptr_null];
129    let const_struct = cx.const_struct(&args, false);
130    let arr = cx.const_array(cx.val_ty(const_struct), &[const_struct]);
131    add_global(cx, "llvm.global_ctors", arr, AppendingLinkage);
132}
133
134pub(crate) struct OffloadKernelDims<'ll> {
135    num_workgroups: &'ll Value,
136    threads_per_block: &'ll Value,
137    workgroup_dims: &'ll Value,
138    thread_dims: &'ll Value,
139}
140
141impl<'ll> OffloadKernelDims<'ll> {
142    pub(crate) fn from_operands<'tcx>(
143        builder: &mut Builder<'_, 'll, 'tcx>,
144        workgroup_op: &OperandRef<'tcx, &'ll llvm::Value>,
145        thread_op: &OperandRef<'tcx, &'ll llvm::Value>,
146    ) -> Self {
147        let cx = builder.cx;
148        let arr_ty = cx.type_array(cx.type_i32(), 3);
149        let four = Align::from_bytes(4).unwrap();
150
151        let OperandValue::Ref(place) = workgroup_op.val else {
152            ::rustc_middle::util::bug::bug_fmt(format_args!("expected array operand by reference"));bug!("expected array operand by reference");
153        };
154        let workgroup_val = builder.load(arr_ty, place.llval, four);
155
156        let OperandValue::Ref(place) = thread_op.val else {
157            ::rustc_middle::util::bug::bug_fmt(format_args!("expected array operand by reference"));bug!("expected array operand by reference");
158        };
159        let thread_val = builder.load(arr_ty, place.llval, four);
160
161        fn mul_dim3<'ll, 'tcx>(
162            builder: &mut Builder<'_, 'll, 'tcx>,
163            arr: &'ll Value,
164        ) -> &'ll Value {
165            let x = builder.extract_value(arr, 0);
166            let y = builder.extract_value(arr, 1);
167            let z = builder.extract_value(arr, 2);
168
169            let xy = builder.mul(x, y);
170            builder.mul(xy, z)
171        }
172
173        let num_workgroups = mul_dim3(builder, workgroup_val);
174        let threads_per_block = mul_dim3(builder, thread_val);
175
176        OffloadKernelDims {
177            workgroup_dims: workgroup_val,
178            thread_dims: thread_val,
179            num_workgroups,
180            threads_per_block,
181        }
182    }
183}
184
185// ; Function Attrs: nounwind
186// declare i32 @__tgt_target_kernel(ptr, i64, i32, i32, ptr, ptr) #2
187fn generate_launcher<'ll>(cx: &CodegenCx<'ll, '_>) -> (&'ll llvm::Value, &'ll llvm::Type) {
188    let tptr = cx.type_ptr();
189    let ti64 = cx.type_i64();
190    let ti32 = cx.type_i32();
191    let args = ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [tptr, ti64, ti32, ti32, tptr, tptr]))vec![tptr, ti64, ti32, ti32, tptr, tptr];
192    let tgt_fn_ty = cx.type_func(&args, ti32);
193    let name = "__tgt_target_kernel";
194    let tgt_decl = declare_offload_fn(&cx, name, tgt_fn_ty);
195    let nounwind = llvm::AttributeKind::NoUnwind.create_attr(cx.llcx);
196    attributes::apply_to_llfn(tgt_decl, Function, &[nounwind]);
197    (tgt_decl, tgt_fn_ty)
198}
199
200/// Declares the `omp_get_num_devices` runtime function and returns the
201/// declaration together with its type.
202pub(crate) fn declare_omp_get_num_devices<'ll>(
203    cx: &CodegenCx<'ll, '_>,
204) -> (&'ll llvm::Value, &'ll llvm::Type) {
205    let ti32 = cx.type_i32();
206    let tgt_fn_ty = cx.type_func(&[], ti32);
207    let name = "omp_get_num_devices";
208    let tgt_decl = declare_offload_fn(&cx, name, tgt_fn_ty);
209    let nounwind = llvm::AttributeKind::NoUnwind.create_attr(cx.llcx);
210    attributes::apply_to_llfn(tgt_decl, Function, &[nounwind]);
211    (tgt_decl, tgt_fn_ty)
212}
213
214// What is our @1 here? A magic global, used in our data_{begin/update/end}_mapper:
215// @0 = private unnamed_addr constant [23 x i8] c";unknown;unknown;0;0;;\00", align 1
216// @1 = private unnamed_addr constant %struct.ident_t { i32 0, i32 2, i32 0, i32 22, ptr @0 }, align 8
217// FIXME(offload): @0 should include the file name (e.g. lib.rs) in which the function to be
218// offloaded was defined.
219pub(crate) fn generate_at_one<'ll>(cx: &CodegenCx<'ll, '_>) -> &'ll llvm::Value {
220    let unknown_txt = ";unknown;unknown;0;0;;";
221    let c_entry_name = CString::new(unknown_txt).unwrap();
222    let c_val = c_entry_name.as_bytes_with_nul();
223    let initializer = crate::common::bytes_in_context(cx.llcx, c_val);
224    let at_zero = add_unnamed_global(&cx, &"", initializer, PrivateLinkage);
225    llvm::set_alignment(at_zero, Align::ONE);
226
227    // @1 = private unnamed_addr constant %struct.ident_t { i32 0, i32 2, i32 0, i32 22, ptr @0 }, align 8
228    let struct_ident_ty = cx.type_named_struct("struct.ident_t");
229    let struct_elems = ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [cx.get_const_i32(0), cx.get_const_i32(2), cx.get_const_i32(0),
                cx.get_const_i32(22), at_zero]))vec![
230        cx.get_const_i32(0),
231        cx.get_const_i32(2),
232        cx.get_const_i32(0),
233        cx.get_const_i32(22),
234        at_zero,
235    ];
236    let struct_elems_ty: Vec<_> = struct_elems.iter().map(|&x| cx.val_ty(x)).collect();
237    let initializer = crate::common::named_struct(struct_ident_ty, &struct_elems);
238    cx.set_struct_body(struct_ident_ty, &struct_elems_ty, false);
239    let at_one = add_unnamed_global(&cx, &"", initializer, PrivateLinkage);
240    llvm::set_alignment(at_one, Align::EIGHT);
241    at_one
242}
243
244pub(crate) struct TgtOffloadEntry {
245    //   uint64_t Reserved;
246    //   uint16_t Version;
247    //   uint16_t Kind;
248    //   uint32_t Flags; Flags associated with the entry (see Target Region Entry Flags)
249    //   void *Address; Address of global symbol within device image (function or global)
250    //   char *SymbolName;
251    //   uint64_t Size; Size of the entry info (0 if it is a function)
252    //   uint64_t Data;
253    //   void *AuxAddr;
254}
255
256impl TgtOffloadEntry {
257    pub(crate) fn new_decl<'ll>(cx: &CodegenCx<'ll, '_>) -> &'ll llvm::Type {
258        let offload_entry_ty = cx.type_named_struct("struct.__tgt_offload_entry");
259        let tptr = cx.type_ptr();
260        let ti64 = cx.type_i64();
261        let ti32 = cx.type_i32();
262        let ti16 = cx.type_i16();
263        // For each kernel to run on the gpu, we will later generate one entry of this type.
264        // copied from LLVM
265        let entry_elements = ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [ti64, ti16, ti16, ti32, tptr, tptr, ti64, ti64, tptr]))vec![ti64, ti16, ti16, ti32, tptr, tptr, ti64, ti64, tptr];
266        cx.set_struct_body(offload_entry_ty, &entry_elements, false);
267        offload_entry_ty
268    }
269
270    fn new<'ll>(
271        cx: &CodegenCx<'ll, '_>,
272        region_id: &'ll Value,
273        llglobal: &'ll Value,
274    ) -> [&'ll Value; 9] {
275        let reserved = cx.get_const_i64(0);
276        let version = cx.get_const_i16(1);
277        let kind = cx.get_const_i16(1);
278        let flags = cx.get_const_i32(0);
279        let size = cx.get_const_i64(0);
280        let data = cx.get_const_i64(0);
281        let aux_addr = cx.const_null(cx.type_ptr());
282        [reserved, version, kind, flags, region_id, llglobal, size, data, aux_addr]
283    }
284}
285
286// Taken from the LLVM APITypes.h declaration:
287struct KernelArgsTy {
288    //  uint32_t Version = 0; // Version of this struct for ABI compatibility.
289    //  uint32_t NumArgs = 0; // Number of arguments in each input pointer.
290    //  void **ArgBasePtrs =
291    //      nullptr;                 // Base pointer of each argument (e.g. a struct).
292    //  void **ArgPtrs = nullptr;    // Pointer to the argument data.
293    //  int64_t *ArgSizes = nullptr; // Size of the argument data in bytes.
294    //  int64_t *ArgTypes = nullptr; // Type of the data (e.g. to / from).
295    //  void **ArgNames = nullptr;   // Name of the data for debugging, possibly null.
296    //  void **ArgMappers = nullptr; // User-defined mappers, possibly null.
297    //  uint64_t Tripcount =
298    // 0; // Tripcount for the teams / distribute loop, 0 otherwise.
299    // struct {
300    //    uint64_t NoWait : 1; // Was this kernel spawned with a `nowait` clause.
301    //    uint64_t IsCUDA : 1; // Was this kernel spawned via CUDA.
302    //    uint64_t Unused : 62;
303    //  } Flags = {0, 0, 0}; // totals to 64 Bit, 8 Byte
304    //  // The number of teams (for x,y,z dimension).
305    //  uint32_t NumTeams[3] = {0, 0, 0};
306    //  // The number of threads (for x,y,z dimension).
307    //  uint32_t ThreadLimit[3] = {0, 0, 0};
308    //  uint32_t DynCGroupMem = 0; // Amount of dynamic cgroup memory requested.
309}
310
311impl KernelArgsTy {
312    const OFFLOAD_VERSION: u64 = 3;
313    const FLAGS: u64 = 1 << 6; // Enable StrictBlocksAndThreads
314    const TRIPCOUNT: u64 = 0;
315    fn new_decl<'ll>(cx: &CodegenCx<'ll, '_>) -> &'ll Type {
316        let kernel_arguments_ty = cx.type_named_struct("struct.__tgt_kernel_arguments");
317        let tptr = cx.type_ptr();
318        let ti64 = cx.type_i64();
319        let ti32 = cx.type_i32();
320        let tarr = cx.type_array(ti32, 3);
321
322        let kernel_elements =
323            ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [ti32, ti32, tptr, tptr, tptr, tptr, tptr, tptr, ti64, ti64, tarr,
                tarr, ti32]))vec![ti32, ti32, tptr, tptr, tptr, tptr, tptr, tptr, ti64, ti64, tarr, tarr, ti32];
324
325        cx.set_struct_body(kernel_arguments_ty, &kernel_elements, false);
326        kernel_arguments_ty
327    }
328
329    fn new<'ll, 'tcx>(
330        cx: &CodegenCx<'ll, 'tcx>,
331        num_args: u64,
332        memtransfer_types: &'ll Value,
333        geps: [&'ll Value; 3],
334        workgroup_dims: &'ll Value,
335        thread_dims: &'ll Value,
336        dyn_cache: &'ll Value,
337    ) -> [(Align, &'ll str, &'ll Value); 13] {
338        let four = Align::from_bytes(4).expect("4 Byte alignment should work");
339        let eight = Align::EIGHT;
340
341        [
342            (four, "Version", cx.get_const_i32(KernelArgsTy::OFFLOAD_VERSION)),
343            (four, "NumArgs", cx.get_const_i32(num_args)),
344            (eight, "ArgBasePtrs", geps[0]),
345            (eight, "ArgPtrs", geps[1]),
346            (eight, "ArgSizes", geps[2]),
347            (eight, "ArgTypes", memtransfer_types),
348            // The next two are debug infos. FIXME(offload): set them
349            (eight, "ArgNames", cx.const_null(cx.type_ptr())), // dbg
350            (eight, "ArgMappers", cx.const_null(cx.type_ptr())), // dbg
351            (eight, "Tripcount", cx.get_const_i64(KernelArgsTy::TRIPCOUNT)),
352            (eight, "Flags", cx.get_const_i64(KernelArgsTy::FLAGS)),
353            (four, "NumTeams", workgroup_dims),
354            (four, "ThreadLimit", thread_dims),
355            (four, "DynCGroupMem", dyn_cache),
356        ]
357    }
358}
359
360// Contains LLVM values needed to manage offloading for a single kernel.
361#[derive(#[automatically_derived]
impl<'ll> ::core::marker::Copy for OffloadKernelGlobals<'ll> { }Copy, #[automatically_derived]
impl<'ll> ::core::clone::Clone for OffloadKernelGlobals<'ll> {
    #[inline]
    fn clone(&self) -> OffloadKernelGlobals<'ll> {
        let _: ::core::clone::AssertParamIsClone<&'ll llvm::Value>;
        let _: ::core::clone::AssertParamIsClone<&'ll llvm::Value>;
        let _: ::core::clone::AssertParamIsClone<&'ll llvm::Value>;
        let _: ::core::clone::AssertParamIsClone<&'ll llvm::Value>;
        let _: ::core::clone::AssertParamIsClone<&'ll llvm::Value>;
        *self
    }
}Clone)]
362pub(crate) struct OffloadKernelGlobals<'ll> {
363    pub offload_sizes: &'ll llvm::Value,
364    pub memtransfer_begin: &'ll llvm::Value,
365    pub memtransfer_kernel: &'ll llvm::Value,
366    pub memtransfer_end: &'ll llvm::Value,
367    pub region_id: &'ll llvm::Value,
368}
369
370fn gen_tgt_data_mappers<'ll>(
371    cx: &CodegenCx<'ll, '_>,
372) -> (&'ll llvm::Value, &'ll llvm::Value, &'ll llvm::Value, &'ll llvm::Type) {
373    let tptr = cx.type_ptr();
374    let ti64 = cx.type_i64();
375    let ti32 = cx.type_i32();
376
377    let args = ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [tptr, ti64, ti32, tptr, tptr, tptr, tptr, tptr, tptr]))vec![tptr, ti64, ti32, tptr, tptr, tptr, tptr, tptr, tptr];
378    let mapper_fn_ty = cx.type_func(&args, cx.type_void());
379    let mapper_begin = "__tgt_target_data_begin_mapper";
380    let mapper_update = "__tgt_target_data_update_mapper";
381    let mapper_end = "__tgt_target_data_end_mapper";
382    let begin_mapper_decl = declare_offload_fn(&cx, mapper_begin, mapper_fn_ty);
383    let update_mapper_decl = declare_offload_fn(&cx, mapper_update, mapper_fn_ty);
384    let end_mapper_decl = declare_offload_fn(&cx, mapper_end, mapper_fn_ty);
385
386    let nounwind = llvm::AttributeKind::NoUnwind.create_attr(cx.llcx);
387    attributes::apply_to_llfn(begin_mapper_decl, Function, &[nounwind]);
388    attributes::apply_to_llfn(update_mapper_decl, Function, &[nounwind]);
389    attributes::apply_to_llfn(end_mapper_decl, Function, &[nounwind]);
390
391    (begin_mapper_decl, update_mapper_decl, end_mapper_decl, mapper_fn_ty)
392}
393
394fn add_priv_unnamed_arr<'ll>(cx: &SimpleCx<'ll>, name: &str, vals: &[u64]) -> &'ll llvm::Value {
395    let ti64 = cx.type_i64();
396    let mut size_val = Vec::with_capacity(vals.len());
397    for &val in vals {
398        size_val.push(cx.get_const_i64(val));
399    }
400    let initializer = cx.const_array(ti64, &size_val);
401    add_unnamed_global(cx, name, initializer, PrivateLinkage)
402}
403
404pub(crate) fn add_unnamed_global<'ll>(
405    cx: &SimpleCx<'ll>,
406    name: &str,
407    initializer: &'ll llvm::Value,
408    l: Linkage,
409) -> &'ll llvm::Value {
410    let llglobal = add_global(cx, name, initializer, l);
411    llvm::LLVMSetUnnamedAddress(llglobal, llvm::UnnamedAddr::Global);
412    llglobal
413}
414
415pub(crate) fn add_global<'ll>(
416    cx: &SimpleCx<'ll>,
417    name: &str,
418    initializer: &'ll llvm::Value,
419    l: Linkage,
420) -> &'ll llvm::Value {
421    let c_name = CString::new(name).unwrap();
422    let llglobal: &'ll llvm::Value = llvm::add_global(cx.llmod, cx.val_ty(initializer), &c_name);
423    llvm::set_global_constant(llglobal, true);
424    llvm::set_linkage(llglobal, l);
425    llvm::set_initializer(llglobal, initializer);
426    llglobal
427}
428
429// This function returns a memtransfer value which encodes how arguments to this kernel shall be
430// mapped to/from the gpu. It also returns a region_id with the name of this kernel, to be
431// concatenated into the list of region_ids.
432pub(crate) fn gen_define_handling<'ll>(
433    cx: &CodegenCx<'ll, '_>,
434    metadata: &[OffloadMetadata],
435    symbol: String,
436    offload_globals: &OffloadGlobals<'ll>,
437) -> OffloadKernelGlobals<'ll> {
438    if let Some(entry) = cx.offload_kernel_cache.borrow().get(&symbol) {
439        return *entry;
440    }
441
442    let offload_entry_ty = offload_globals.offload_entry_ty;
443
444    let (sizes, transfer): (Vec<_>, Vec<_>) =
445        metadata.iter().map(|m| (m.payload_size, m.mode)).unzip();
446    // Our begin mapper should only see simplified information about which args have to be
447    // transferred to the device, the end mapper only about which args should be transferred back.
448    // Any information beyond that makes it harder for LLVM's opt pass to evaluate whether it can
449    // safely move (=optimize) the LLVM-IR location of this data transfer. Only the mapping types
450    // mentioned below are handled, so make sure that we don't generate any other ones.
451    let handled_mappings = MappingFlags::TO
452        | MappingFlags::FROM
453        | MappingFlags::TARGET_PARAM
454        | MappingFlags::LITERAL
455        | MappingFlags::IMPLICIT;
456    for arg in &transfer {
457        if true {
    if !!arg.contains_unknown_bits() {
        ::core::panicking::panic("assertion failed: !arg.contains_unknown_bits()")
    };
};debug_assert!(!arg.contains_unknown_bits());
458        if true {
    if !handled_mappings.contains(*arg) {
        ::core::panicking::panic("assertion failed: handled_mappings.contains(*arg)")
    };
};debug_assert!(handled_mappings.contains(*arg));
459    }
460
461    let valid_begin_mappings = MappingFlags::TO | MappingFlags::LITERAL | MappingFlags::IMPLICIT;
462    let transfer_to: Vec<u64> =
463        transfer.iter().map(|m| m.intersection(valid_begin_mappings).bits()).collect();
464    let transfer_from: Vec<u64> =
465        transfer.iter().map(|m| m.intersection(MappingFlags::FROM).bits()).collect();
466    let valid_kernel_mappings = MappingFlags::LITERAL | MappingFlags::IMPLICIT;
467    // FIXME(offload): add `OMP_MAP_TARGET_PARAM = 0x20` only if necessary
468    let transfer_kernel: Vec<u64> = transfer
469        .iter()
470        .map(|m| (m.intersection(valid_kernel_mappings) | MappingFlags::TARGET_PARAM).bits())
471        .collect();
472
473    let actual_sizes = sizes
474        .iter()
475        .map(|s| match s {
476            OffloadSize::Static(sz) => *sz,
477            // NOTE(Sa4dUs): set `.offload_sizes` entry to 0 for sizes that we determine at runtime, just like clang
478            _ => 0,
479        })
480        .collect::<Vec<_>>();
481    let offload_sizes =
482        add_priv_unnamed_arr(&cx, &::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(".offload_sizes.{0}", symbol))
    })format!(".offload_sizes.{symbol}"), &actual_sizes);
483    let memtransfer_begin =
484        add_priv_unnamed_arr(&cx, &::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(".offload_maptypes.{0}.begin",
                symbol))
    })format!(".offload_maptypes.{symbol}.begin"), &transfer_to);
485    let memtransfer_kernel =
486        add_priv_unnamed_arr(&cx, &::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(".offload_maptypes.{0}.kernel",
                symbol))
    })format!(".offload_maptypes.{symbol}.kernel"), &transfer_kernel);
487    let memtransfer_end =
488        add_priv_unnamed_arr(&cx, &::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(".offload_maptypes.{0}.end",
                symbol))
    })format!(".offload_maptypes.{symbol}.end"), &transfer_from);
489
490    // Next: For each function, generate these three entries. A weak constant,
491    // the llvm.rodata entry name, and  the llvm_offload_entries value
492
493    let name = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(".{0}.region_id", symbol))
    })format!(".{symbol}.region_id");
494    let initializer = cx.get_const_i8(0);
495    let region_id = add_global(&cx, &name, initializer, WeakAnyLinkage);
496
497    let c_entry_name = CString::new(symbol.clone()).unwrap();
498    let c_val = c_entry_name.as_bytes_with_nul();
499    let offload_entry_name = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(".offloading.entry_name.{0}",
                symbol))
    })format!(".offloading.entry_name.{symbol}");
500
501    let initializer = crate::common::bytes_in_context(cx.llcx, c_val);
502    let llglobal = add_unnamed_global(&cx, &offload_entry_name, initializer, InternalLinkage);
503    llvm::set_alignment(llglobal, Align::ONE);
504    llvm::set_section(llglobal, c".llvm.rodata.offloading");
505
506    let name = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(".offloading.entry.{0}", symbol))
    })format!(".offloading.entry.{symbol}");
507
508    // See the __tgt_offload_entry documentation above.
509    let elems = TgtOffloadEntry::new(&cx, region_id, llglobal);
510
511    let initializer = crate::common::named_struct(offload_entry_ty, &elems);
512    let c_name = CString::new(name).unwrap();
513    let offload_entry = llvm::add_global(cx.llmod, offload_entry_ty, &c_name);
514    llvm::set_global_constant(offload_entry, true);
515    llvm::set_linkage(offload_entry, WeakAnyLinkage);
516    llvm::set_initializer(offload_entry, initializer);
517    llvm::set_alignment(offload_entry, Align::EIGHT);
518    let c_section_name = CString::new("llvm_offload_entries").unwrap();
519    llvm::set_section(offload_entry, &c_section_name);
520
521    cx.add_compiler_used_global(offload_entry);
522
523    let result = OffloadKernelGlobals {
524        offload_sizes,
525        memtransfer_begin,
526        memtransfer_kernel,
527        memtransfer_end,
528        region_id,
529    };
530
531    cx.offload_kernel_cache.borrow_mut().insert(symbol, result);
532
533    result
534}
535
536fn declare_offload_fn<'ll>(
537    cx: &CodegenCx<'ll, '_>,
538    name: &str,
539    ty: &'ll llvm::Type,
540) -> &'ll llvm::Value {
541    crate::declare::declare_simple_fn(
542        cx,
543        name,
544        llvm::CallConv::CCallConv,
545        llvm::UnnamedAddr::No,
546        llvm::Visibility::Default,
547        ty,
548    )
549}
550
551pub(crate) fn scalar_width<'ll>(cx: &'ll SimpleCx<'_>, ty: &'ll Type) -> u64 {
552    match cx.type_kind(ty) {
553        TypeKind::Half
554        | TypeKind::Float
555        | TypeKind::Double
556        | TypeKind::X86_FP80
557        | TypeKind::FP128
558        | TypeKind::PPC_FP128 => cx.float_width(ty) as u64,
559        TypeKind::Integer => cx.int_width(ty),
560        other => ::rustc_middle::util::bug::bug_fmt(format_args!("scalar_width was called on a non scalar type {0:?}",
        other))bug!("scalar_width was called on a non scalar type {other:?}"),
561    }
562}
563
564fn get_runtime_size<'ll, 'tcx>(
565    builder: &mut Builder<'_, 'll, 'tcx>,
566    args: &[&'ll Value],
567    index: usize,
568    meta: &OffloadMetadata,
569) -> &'ll Value {
570    match meta.payload_size {
571        OffloadSize::Slice { element_size } => {
572            let length_idx = index + 1;
573            let length = args[length_idx];
574            let length_i64 = builder.intcast(length, builder.cx.type_i64(), false);
575            builder.mul(length_i64, builder.cx.get_const_i64(element_size))
576        }
577        _ => ::rustc_middle::util::bug::bug_fmt(format_args!("unexpected offload size {0:?}",
        meta.payload_size))bug!("unexpected offload size {:?}", meta.payload_size),
578    }
579}
580
581// For each kernel *call*, we now use some of our previous declared globals to move data to and from
582// the gpu. For now, we only handle the data transfer part of it.
583// If two consecutive kernels use the same memory, we still move it to the host and back to the gpu.
584// Since in our frontend users (by default) don't have to specify data transfer, this is something
585// we should optimize in the future! In some cases we can directly zero-allocate on the device and
586// only move data back, or if something is immutable, we might only copy it to the device.
587//
588// Current steps:
589// 0. Alloca some variables for the following steps
590// 1. set insert point before kernel call.
591// 2. generate all the GEPS and stores, to be used in 3)
592// 3. generate __tgt_target_data_begin calls to move data to the GPU
593//
594// unchanged: keep kernel call. Later move the kernel to the GPU
595//
596// 4. set insert point after kernel call.
597// 5. generate all the GEPS and stores, to be used in 6)
598// 6. generate __tgt_target_data_end calls to move data from the GPU
599pub(crate) fn gen_call_handling<'ll, 'tcx>(
600    builder: &mut Builder<'_, 'll, 'tcx>,
601    offload_data: &OffloadKernelGlobals<'ll>,
602    args: &[&'ll Value],
603    types: &[&Type],
604    metadata: &[OffloadMetadata],
605    offload_globals: &OffloadGlobals<'ll>,
606    offload_dims: &OffloadKernelDims<'ll>,
607    dyn_cache: &'ll Value,
608    device_id: &'ll Value,
609) {
610    let cx = builder.cx;
611    let OffloadKernelGlobals {
612        offload_sizes,
613        memtransfer_begin,
614        memtransfer_kernel,
615        memtransfer_end,
616        region_id,
617    } = offload_data;
618    let OffloadKernelDims { num_workgroups, threads_per_block, workgroup_dims, thread_dims } =
619        offload_dims;
620
621    let has_dynamic = metadata.iter().any(|m| !#[allow(non_exhaustive_omitted_patterns)] match m.payload_size {
    OffloadSize::Static(_) => true,
    _ => false,
}matches!(m.payload_size, OffloadSize::Static(_)));
622
623    let tgt_decl = offload_globals.launcher_fn;
624    let tgt_target_kernel_ty = offload_globals.launcher_ty;
625
626    let tgt_kernel_decl = offload_globals.kernel_args_ty;
627    let begin_mapper_decl = offload_globals.begin_mapper;
628    let end_mapper_decl = offload_globals.end_mapper;
629    let fn_ty = offload_globals.mapper_fn_ty;
630
631    let num_args = types.len() as u64;
632    let bb = builder.llbb();
633
634    // Step 0)
635    unsafe {
636        llvm::LLVMRustPositionBuilderPastAllocas(&builder.llbuilder, builder.llfn());
637    }
638
639    let ty = cx.type_array(cx.type_ptr(), num_args);
640    // Baseptr are just the input pointer to the kernel, stored in a local alloca
641    let a1 = builder.direct_alloca(ty, Align::EIGHT, ".offload_baseptrs");
642    // Ptrs are the result of a gep into the baseptr, at least for our trivial types.
643    let a2 = builder.direct_alloca(ty, Align::EIGHT, ".offload_ptrs");
644    // These represent the sizes in bytes, e.g. the entry for `&[f64; 16]` will be 8*16.
645    let ty2 = cx.type_array(cx.type_i64(), num_args);
646
647    let a4 = if has_dynamic {
648        let alloc = builder.direct_alloca(ty2, Align::EIGHT, ".offload_sizes");
649
650        builder.memcpy(
651            alloc,
652            Align::EIGHT,
653            offload_sizes,
654            Align::EIGHT,
655            cx.get_const_i64(8 * args.len() as u64),
656            MemFlags::empty(),
657            None,
658        );
659
660        alloc
661    } else {
662        offload_sizes
663    };
664
665    //%kernel_args = alloca %struct.__tgt_kernel_arguments, align 8
666    let a5 = builder.direct_alloca(tgt_kernel_decl, Align::EIGHT, "kernel_args");
667
668    // Step 1)
669    unsafe {
670        llvm::LLVMPositionBuilderAtEnd(&builder.llbuilder, bb);
671    }
672
673    // Now we allocate once per function param, a copy to be passed to one of our maps.
674    let mut vals = ::alloc::vec::Vec::new()vec![];
675    let mut geps = ::alloc::vec::Vec::new()vec![];
676    let i32_0 = cx.get_const_i32(0);
677    for &v in args {
678        let ty = cx.val_ty(v);
679        let ty_kind = cx.type_kind(ty);
680        let (base_val, gep_base) = match ty_kind {
681            TypeKind::Pointer => (v, v),
682            TypeKind::Half | TypeKind::Float | TypeKind::Double | TypeKind::Integer => {
683                // FIXME(Sa4dUs): check for `f128` support, latest NVIDIA cards support it
684                let num_bits = scalar_width(cx, ty);
685
686                let bb = builder.llbb();
687                unsafe {
688                    llvm::LLVMRustPositionBuilderPastAllocas(builder.llbuilder, builder.llfn());
689                }
690                let addr = builder.direct_alloca(cx.type_i64(), Align::EIGHT, "addr");
691                unsafe {
692                    llvm::LLVMPositionBuilderAtEnd(builder.llbuilder, bb);
693                }
694
695                let cast = builder.bitcast(v, cx.type_ix(num_bits));
696                let value = builder.zext(cast, cx.type_i64());
697                builder.store(value, addr, Align::EIGHT);
698                (value, addr)
699            }
700            other => ::rustc_middle::util::bug::bug_fmt(format_args!("offload does not support {0:?}",
        other))bug!("offload does not support {other:?}"),
701        };
702
703        let gep = builder.inbounds_gep(cx.type_f32(), gep_base, &[i32_0]);
704
705        vals.push(base_val);
706        geps.push(gep);
707    }
708
709    for i in 0..num_args {
710        let idx = cx.get_const_i32(i);
711        let gep1 = builder.inbounds_gep(ty, a1, &[i32_0, idx]);
712        builder.store(vals[i as usize], gep1, Align::EIGHT);
713        let gep2 = builder.inbounds_gep(ty, a2, &[i32_0, idx]);
714        builder.store(geps[i as usize], gep2, Align::EIGHT);
715
716        if !#[allow(non_exhaustive_omitted_patterns)] match metadata[i as
                usize].payload_size {
    OffloadSize::Static(_) => true,
    _ => false,
}matches!(metadata[i as usize].payload_size, OffloadSize::Static(_)) {
717            let gep3 = builder.inbounds_gep(ty2, a4, &[i32_0, idx]);
718            let size_val = get_runtime_size(builder, args, i as usize, &metadata[i as usize]);
719            builder.store(size_val, gep3, Align::EIGHT);
720        }
721    }
722
723    // For now we have a very simplistic indexing scheme into our
724    // offload_{baseptrs,ptrs,sizes}. We will probably improve this along with our gpu frontend pr.
725    fn get_geps<'ll, 'tcx>(
726        builder: &mut Builder<'_, 'll, 'tcx>,
727        ty: &'ll Type,
728        ty2: &'ll Type,
729        a1: &'ll Value,
730        a2: &'ll Value,
731        a4: &'ll Value,
732        is_dynamic: bool,
733    ) -> [&'ll Value; 3] {
734        let cx = builder.cx;
735        let i32_0 = cx.get_const_i32(0);
736
737        let gep1 = builder.inbounds_gep(ty, a1, &[i32_0, i32_0]);
738        let gep2 = builder.inbounds_gep(ty, a2, &[i32_0, i32_0]);
739        let gep3 = if is_dynamic { builder.inbounds_gep(ty2, a4, &[i32_0, i32_0]) } else { a4 };
740        [gep1, gep2, gep3]
741    }
742
743    fn generate_mapper_call<'ll, 'tcx>(
744        builder: &mut Builder<'_, 'll, 'tcx>,
745        geps: [&'ll Value; 3],
746        o_type: &'ll Value,
747        fn_to_call: &'ll Value,
748        fn_ty: &'ll Type,
749        num_args: u64,
750        s_ident_t: &'ll Value,
751    ) {
752        let cx = builder.cx;
753        let nullptr = cx.const_null(cx.type_ptr());
754        let i64_max = cx.get_const_i64(u64::MAX);
755        let num_args = cx.get_const_i32(num_args);
756        let args =
757            ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [s_ident_t, i64_max, num_args, geps[0], geps[1], geps[2], o_type,
                nullptr, nullptr]))vec![s_ident_t, i64_max, num_args, geps[0], geps[1], geps[2], o_type, nullptr, nullptr];
758        builder.call(fn_ty, None, None, fn_to_call, &args, None, None);
759    }
760
761    // Step 2)
762    let s_ident_t = offload_globals.ident_t_global;
763    let geps = get_geps(builder, ty, ty2, a1, a2, a4, has_dynamic);
764    generate_mapper_call(
765        builder,
766        geps,
767        memtransfer_begin,
768        begin_mapper_decl,
769        fn_ty,
770        num_args,
771        s_ident_t,
772    );
773    let values = KernelArgsTy::new(
774        &cx,
775        num_args,
776        memtransfer_kernel,
777        geps,
778        workgroup_dims,
779        thread_dims,
780        dyn_cache,
781    );
782
783    // Step 3)
784    // Here we fill the KernelArgsTy, see the documentation above
785    for (i, value) in values.iter().enumerate() {
786        let ptr = builder.inbounds_gep(tgt_kernel_decl, a5, &[i32_0, cx.get_const_i32(i as u64)]);
787        let name = std::ffi::CString::new(value.1).unwrap();
788        llvm::set_value_name(ptr, &name.as_bytes());
789
790        builder.store(value.2, ptr, value.0);
791    }
792
793    let device_id = builder.sext(device_id, cx.type_i64());
794    let args = ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [s_ident_t, device_id, num_workgroups, threads_per_block, region_id,
                a5]))vec![s_ident_t, device_id, num_workgroups, threads_per_block, region_id, a5];
795    builder.call(tgt_target_kernel_ty, None, None, tgt_decl, &args, None, None);
796    // %41 = call i32 @__tgt_target_kernel(ptr @1, i64 -1, i32 2097152, i32 256, ptr @.kernel_1.region_id, ptr %kernel_args)
797
798    // Step 4)
799    let geps = get_geps(builder, ty, ty2, a1, a2, a4, has_dynamic);
800    generate_mapper_call(
801        builder,
802        geps,
803        memtransfer_end,
804        end_mapper_decl,
805        fn_ty,
806        num_args,
807        s_ident_t,
808    );
809}