1 //===- OffloadWrapper.cpp ---------------------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "OffloadWrapper.h" 10 #include "llvm/ADT/ArrayRef.h" 11 #include "llvm/ADT/Triple.h" 12 #include "llvm/IR/Constants.h" 13 #include "llvm/IR/GlobalVariable.h" 14 #include "llvm/IR/IRBuilder.h" 15 #include "llvm/IR/LLVMContext.h" 16 #include "llvm/IR/Module.h" 17 #include "llvm/Support/Error.h" 18 #include "llvm/Transforms/Utils/ModuleUtils.h" 19 20 using namespace llvm; 21 22 namespace { 23 24 IntegerType *getSizeTTy(Module &M) { 25 LLVMContext &C = M.getContext(); 26 switch (M.getDataLayout().getPointerTypeSize(Type::getInt8PtrTy(C))) { 27 case 4u: 28 return Type::getInt32Ty(C); 29 case 8u: 30 return Type::getInt64Ty(C); 31 } 32 llvm_unreachable("unsupported pointer type size"); 33 } 34 35 // struct __tgt_offload_entry { 36 // void *addr; 37 // char *name; 38 // size_t size; 39 // int32_t flags; 40 // int32_t reserved; 41 // }; 42 StructType *getEntryTy(Module &M) { 43 LLVMContext &C = M.getContext(); 44 StructType *EntryTy = StructType::getTypeByName(C, "__tgt_offload_entry"); 45 if (!EntryTy) 46 EntryTy = StructType::create("__tgt_offload_entry", Type::getInt8PtrTy(C), 47 Type::getInt8PtrTy(C), getSizeTTy(M), 48 Type::getInt32Ty(C), Type::getInt32Ty(C)); 49 return EntryTy; 50 } 51 52 PointerType *getEntryPtrTy(Module &M) { 53 return PointerType::getUnqual(getEntryTy(M)); 54 } 55 56 // struct __tgt_device_image { 57 // void *ImageStart; 58 // void *ImageEnd; 59 // __tgt_offload_entry *EntriesBegin; 60 // __tgt_offload_entry *EntriesEnd; 61 // }; 62 StructType *getDeviceImageTy(Module &M) { 63 LLVMContext &C = M.getContext(); 64 StructType *ImageTy = StructType::getTypeByName(C, "__tgt_device_image"); 65 if (!ImageTy) 66 ImageTy = StructType::create("__tgt_device_image", Type::getInt8PtrTy(C), 67 Type::getInt8PtrTy(C), getEntryPtrTy(M), 68 getEntryPtrTy(M)); 69 return ImageTy; 70 } 71 72 PointerType *getDeviceImagePtrTy(Module &M) { 73 return PointerType::getUnqual(getDeviceImageTy(M)); 74 } 75 76 // struct __tgt_bin_desc { 77 // int32_t NumDeviceImages; 78 // __tgt_device_image *DeviceImages; 79 // __tgt_offload_entry *HostEntriesBegin; 80 // __tgt_offload_entry *HostEntriesEnd; 81 // }; 82 StructType *getBinDescTy(Module &M) { 83 LLVMContext &C = M.getContext(); 84 StructType *DescTy = StructType::getTypeByName(C, "__tgt_bin_desc"); 85 if (!DescTy) 86 DescTy = StructType::create("__tgt_bin_desc", Type::getInt32Ty(C), 87 getDeviceImagePtrTy(M), getEntryPtrTy(M), 88 getEntryPtrTy(M)); 89 return DescTy; 90 } 91 92 PointerType *getBinDescPtrTy(Module &M) { 93 return PointerType::getUnqual(getBinDescTy(M)); 94 } 95 96 /// Creates binary descriptor for the given device images. Binary descriptor 97 /// is an object that is passed to the offloading runtime at program startup 98 /// and it describes all device images available in the executable or shared 99 /// library. It is defined as follows 100 /// 101 /// __attribute__((visibility("hidden"))) 102 /// extern __tgt_offload_entry *__start_omp_offloading_entries; 103 /// __attribute__((visibility("hidden"))) 104 /// extern __tgt_offload_entry *__stop_omp_offloading_entries; 105 /// 106 /// static const char Image0[] = { <Bufs.front() contents> }; 107 /// ... 108 /// static const char ImageN[] = { <Bufs.back() contents> }; 109 /// 110 /// static const __tgt_device_image Images[] = { 111 /// { 112 /// Image0, /*ImageStart*/ 113 /// Image0 + sizeof(Image0), /*ImageEnd*/ 114 /// __start_omp_offloading_entries, /*EntriesBegin*/ 115 /// __stop_omp_offloading_entries /*EntriesEnd*/ 116 /// }, 117 /// ... 118 /// { 119 /// ImageN, /*ImageStart*/ 120 /// ImageN + sizeof(ImageN), /*ImageEnd*/ 121 /// __start_omp_offloading_entries, /*EntriesBegin*/ 122 /// __stop_omp_offloading_entries /*EntriesEnd*/ 123 /// } 124 /// }; 125 /// 126 /// static const __tgt_bin_desc BinDesc = { 127 /// sizeof(Images) / sizeof(Images[0]), /*NumDeviceImages*/ 128 /// Images, /*DeviceImages*/ 129 /// __start_omp_offloading_entries, /*HostEntriesBegin*/ 130 /// __stop_omp_offloading_entries /*HostEntriesEnd*/ 131 /// }; 132 /// 133 /// Global variable that represents BinDesc is returned. 134 GlobalVariable *createBinDesc(Module &M, ArrayRef<ArrayRef<char>> Bufs) { 135 LLVMContext &C = M.getContext(); 136 // Create external begin/end symbols for the offload entries table. 137 auto *EntriesB = new GlobalVariable( 138 M, getEntryTy(M), /*isConstant*/ true, GlobalValue::ExternalLinkage, 139 /*Initializer*/ nullptr, "__start_omp_offloading_entries"); 140 EntriesB->setVisibility(GlobalValue::HiddenVisibility); 141 auto *EntriesE = new GlobalVariable( 142 M, getEntryTy(M), /*isConstant*/ true, GlobalValue::ExternalLinkage, 143 /*Initializer*/ nullptr, "__stop_omp_offloading_entries"); 144 EntriesE->setVisibility(GlobalValue::HiddenVisibility); 145 146 // We assume that external begin/end symbols that we have created above will 147 // be defined by the linker. But linker will do that only if linker inputs 148 // have section with "omp_offloading_entries" name which is not guaranteed. 149 // So, we just create dummy zero sized object in the offload entries section 150 // to force linker to define those symbols. 151 auto *DummyInit = 152 ConstantAggregateZero::get(ArrayType::get(getEntryTy(M), 0u)); 153 auto *DummyEntry = new GlobalVariable( 154 M, DummyInit->getType(), true, GlobalVariable::ExternalLinkage, DummyInit, 155 "__dummy.omp_offloading.entry"); 156 DummyEntry->setSection("omp_offloading_entries"); 157 DummyEntry->setVisibility(GlobalValue::HiddenVisibility); 158 159 auto *Zero = ConstantInt::get(getSizeTTy(M), 0u); 160 Constant *ZeroZero[] = {Zero, Zero}; 161 162 // Create initializer for the images array. 163 SmallVector<Constant *, 4u> ImagesInits; 164 ImagesInits.reserve(Bufs.size()); 165 for (ArrayRef<char> Buf : Bufs) { 166 auto *Data = ConstantDataArray::get(C, Buf); 167 auto *Image = new GlobalVariable(M, Data->getType(), /*isConstant*/ true, 168 GlobalVariable::InternalLinkage, Data, 169 ".omp_offloading.device_image"); 170 Image->setUnnamedAddr(GlobalValue::UnnamedAddr::Global); 171 172 auto *Size = ConstantInt::get(getSizeTTy(M), Buf.size()); 173 Constant *ZeroSize[] = {Zero, Size}; 174 175 auto *ImageB = 176 ConstantExpr::getGetElementPtr(Image->getValueType(), Image, ZeroZero); 177 auto *ImageE = 178 ConstantExpr::getGetElementPtr(Image->getValueType(), Image, ZeroSize); 179 180 ImagesInits.push_back(ConstantStruct::get(getDeviceImageTy(M), ImageB, 181 ImageE, EntriesB, EntriesE)); 182 } 183 184 // Then create images array. 185 auto *ImagesData = ConstantArray::get( 186 ArrayType::get(getDeviceImageTy(M), ImagesInits.size()), ImagesInits); 187 188 auto *Images = 189 new GlobalVariable(M, ImagesData->getType(), /*isConstant*/ true, 190 GlobalValue::InternalLinkage, ImagesData, 191 ".omp_offloading.device_images"); 192 Images->setUnnamedAddr(GlobalValue::UnnamedAddr::Global); 193 194 auto *ImagesB = 195 ConstantExpr::getGetElementPtr(Images->getValueType(), Images, ZeroZero); 196 197 // And finally create the binary descriptor object. 198 auto *DescInit = ConstantStruct::get( 199 getBinDescTy(M), 200 ConstantInt::get(Type::getInt32Ty(C), ImagesInits.size()), ImagesB, 201 EntriesB, EntriesE); 202 203 return new GlobalVariable(M, DescInit->getType(), /*isConstant*/ true, 204 GlobalValue::InternalLinkage, DescInit, 205 ".omp_offloading.descriptor"); 206 } 207 208 void createRegisterFunction(Module &M, GlobalVariable *BinDesc) { 209 LLVMContext &C = M.getContext(); 210 auto *FuncTy = FunctionType::get(Type::getVoidTy(C), /*isVarArg*/ false); 211 auto *Func = Function::Create(FuncTy, GlobalValue::InternalLinkage, 212 ".omp_offloading.descriptor_reg", &M); 213 Func->setSection(".text.startup"); 214 215 // Get __tgt_register_lib function declaration. 216 auto *RegFuncTy = FunctionType::get(Type::getVoidTy(C), getBinDescPtrTy(M), 217 /*isVarArg*/ false); 218 FunctionCallee RegFuncC = 219 M.getOrInsertFunction("__tgt_register_lib", RegFuncTy); 220 221 // Construct function body 222 IRBuilder<> Builder(BasicBlock::Create(C, "entry", Func)); 223 Builder.CreateCall(RegFuncC, BinDesc); 224 Builder.CreateRetVoid(); 225 226 // Add this function to constructors. 227 // Set priority to 1 so that __tgt_register_lib is executed AFTER 228 // __tgt_register_requires (we want to know what requirements have been 229 // asked for before we load a libomptarget plugin so that by the time the 230 // plugin is loaded it can report how many devices there are which can 231 // satisfy these requirements). 232 appendToGlobalCtors(M, Func, /*Priority*/ 1); 233 } 234 235 void createUnregisterFunction(Module &M, GlobalVariable *BinDesc) { 236 LLVMContext &C = M.getContext(); 237 auto *FuncTy = FunctionType::get(Type::getVoidTy(C), /*isVarArg*/ false); 238 auto *Func = Function::Create(FuncTy, GlobalValue::InternalLinkage, 239 ".omp_offloading.descriptor_unreg", &M); 240 Func->setSection(".text.startup"); 241 242 // Get __tgt_unregister_lib function declaration. 243 auto *UnRegFuncTy = FunctionType::get(Type::getVoidTy(C), getBinDescPtrTy(M), 244 /*isVarArg*/ false); 245 FunctionCallee UnRegFuncC = 246 M.getOrInsertFunction("__tgt_unregister_lib", UnRegFuncTy); 247 248 // Construct function body 249 IRBuilder<> Builder(BasicBlock::Create(C, "entry", Func)); 250 Builder.CreateCall(UnRegFuncC, BinDesc); 251 Builder.CreateRetVoid(); 252 253 // Add this function to global destructors. 254 // Match priority of __tgt_register_lib 255 appendToGlobalDtors(M, Func, /*Priority*/ 1); 256 } 257 258 } // namespace 259 260 Error wrapBinaries(Module &M, ArrayRef<ArrayRef<char>> Images) { 261 GlobalVariable *Desc = createBinDesc(M, Images); 262 if (!Desc) 263 return createStringError(inconvertibleErrorCode(), 264 "No binary descriptors created."); 265 createRegisterFunction(M, Desc); 266 createUnregisterFunction(M, Desc); 267 return Error::success(); 268 } 269