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