| //===-RTLs/generic-64bit/src/rtl.cpp - Target RTLs Implementation - C++ -*-===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // RTL NextGen for generic 64-bit machine | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include <cassert> | |
| #include <cstddef> | |
| #include <ffi.h> | |
| #include <string> | |
| #include <unordered_map> | |
| #include "Shared/Debug.h" | |
| #include "Shared/Environment.h" | |
| #include "Utils/ELF.h" | |
| #include "GlobalHandler.h" | |
| #include "OpenMP/OMPT/Callback.h" | |
| #include "PluginInterface.h" | |
| #include "omptarget.h" | |
| #include "llvm/ADT/SmallVector.h" | |
| #include "llvm/Frontend/OpenMP/OMPConstants.h" | |
| #include "llvm/Frontend/OpenMP/OMPDeviceConstants.h" | |
| #include "llvm/Frontend/OpenMP/OMPGridValues.h" | |
| #include "llvm/Support/DynamicLibrary.h" | |
| #if !defined(__BYTE_ORDER__) || !defined(__ORDER_LITTLE_ENDIAN__) || \ | |
| !defined(__ORDER_BIG_ENDIAN__) | |
| #error "Missing preprocessor definitions for endianness detection." | |
| #endif | |
| #if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) | |
| #define LITTLEENDIAN_CPU | |
| #elif defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) | |
| #define BIGENDIAN_CPU | |
| #endif | |
| // The number of devices in this plugin. | |
| #define NUM_DEVICES 4 | |
| namespace llvm { | |
| namespace omp { | |
| namespace target { | |
| namespace plugin { | |
| /// Forward declarations for all specialized data structures. | |
| struct GenELF64KernelTy; | |
| struct GenELF64DeviceTy; | |
| struct GenELF64PluginTy; | |
| using llvm::sys::DynamicLibrary; | |
| /// Class implementing kernel functionalities for GenELF64. | |
| struct GenELF64KernelTy : public GenericKernelTy { | |
| /// Construct the kernel with a name and an execution mode. | |
| GenELF64KernelTy(const char *Name) : GenericKernelTy(Name), Func(nullptr) {} | |
| /// Initialize the kernel. | |
| Error initImpl(GenericDeviceTy &Device, DeviceImageTy &Image) override { | |
| // Functions have zero size. | |
| GlobalTy Global(getName(), 0); | |
| // Get the metadata (address) of the kernel function. | |
| GenericGlobalHandlerTy &GHandler = Device.Plugin.getGlobalHandler(); | |
| if (auto Err = GHandler.getGlobalMetadataFromDevice(Device, Image, Global)) | |
| return Err; | |
| // Check that the function pointer is valid. | |
| if (!Global.getPtr()) | |
| return Plugin::error("Invalid function for kernel %s", getName()); | |
| // Save the function pointer. | |
| Func = (void (*)())Global.getPtr(); | |
| KernelEnvironment.Configuration.ExecMode = OMP_TGT_EXEC_MODE_GENERIC; | |
| KernelEnvironment.Configuration.MayUseNestedParallelism = /*Unknown=*/2; | |
| KernelEnvironment.Configuration.UseGenericStateMachine = /*Unknown=*/2; | |
| // Set the maximum number of threads to a single. | |
| MaxNumThreads = 1; | |
| return Plugin::success(); | |
| } | |
| /// Launch the kernel using the libffi. | |
| Error launchImpl(GenericDeviceTy &GenericDevice, uint32_t NumThreads[3], | |
| uint32_t NumBlocks[3], KernelArgsTy &KernelArgs, | |
| KernelLaunchParamsTy LaunchParams, | |
| AsyncInfoWrapperTy &AsyncInfoWrapper) const override { | |
| // Create a vector of ffi_types, one per argument. | |
| SmallVector<ffi_type *, 16> ArgTypes(KernelArgs.NumArgs, &ffi_type_pointer); | |
| ffi_type **ArgTypesPtr = (ArgTypes.size()) ? &ArgTypes[0] : nullptr; | |
| // Prepare the cif structure before running the kernel function. | |
| ffi_cif Cif; | |
| ffi_status Status = ffi_prep_cif(&Cif, FFI_DEFAULT_ABI, KernelArgs.NumArgs, | |
| &ffi_type_void, ArgTypesPtr); | |
| if (Status != FFI_OK) | |
| return Plugin::error("Error in ffi_prep_cif: %d", Status); | |
| // Call the kernel function through libffi. | |
| long Return; | |
| ffi_call(&Cif, Func, &Return, (void **)LaunchParams.Ptrs); | |
| return Plugin::success(); | |
| } | |
| private: | |
| /// The kernel function to execute. | |
| void (*Func)(void); | |
| }; | |
| /// Class implementing the GenELF64 device images properties. | |
| struct GenELF64DeviceImageTy : public DeviceImageTy { | |
| /// Create the GenELF64 image with the id and the target image pointer. | |
| GenELF64DeviceImageTy(int32_t ImageId, GenericDeviceTy &Device, | |
| const __tgt_device_image *TgtImage) | |
| : DeviceImageTy(ImageId, Device, TgtImage), DynLib() {} | |
| /// Getter and setter for the dynamic library. | |
| DynamicLibrary &getDynamicLibrary() { return DynLib; } | |
| void setDynamicLibrary(const DynamicLibrary &Lib) { DynLib = Lib; } | |
| private: | |
| /// The dynamic library that loaded the image. | |
| DynamicLibrary DynLib; | |
| }; | |
| /// Class implementing the device functionalities for GenELF64. | |
| struct GenELF64DeviceTy : public GenericDeviceTy { | |
| /// Create the device with a specific id. | |
| GenELF64DeviceTy(GenericPluginTy &Plugin, int32_t DeviceId, | |
| int32_t NumDevices) | |
| : GenericDeviceTy(Plugin, DeviceId, NumDevices, GenELF64GridValues) {} | |
| ~GenELF64DeviceTy() {} | |
| /// Initialize the device, which is a no-op | |
| Error initImpl(GenericPluginTy &Plugin) override { return Plugin::success(); } | |
| /// Deinitialize the device, which is a no-op | |
| Error deinitImpl() override { return Plugin::success(); } | |
| /// See GenericDeviceTy::getComputeUnitKind(). | |
| std::string getComputeUnitKind() const override { return "generic-64bit"; } | |
| /// Construct the kernel for a specific image on the device. | |
| Expected<GenericKernelTy &> constructKernel(const char *Name) override { | |
| // Allocate and construct the kernel. | |
| GenELF64KernelTy *GenELF64Kernel = Plugin.allocate<GenELF64KernelTy>(); | |
| if (!GenELF64Kernel) | |
| return Plugin::error("Failed to allocate memory for GenELF64 kernel"); | |
| new (GenELF64Kernel) GenELF64KernelTy(Name); | |
| return *GenELF64Kernel; | |
| } | |
| /// Set the current context to this device, which is a no-op. | |
| Error setContext() override { return Plugin::success(); } | |
| /// Load the binary image into the device and allocate an image object. | |
| Expected<DeviceImageTy *> loadBinaryImpl(const __tgt_device_image *TgtImage, | |
| int32_t ImageId) override { | |
| // Allocate and initialize the image object. | |
| GenELF64DeviceImageTy *Image = Plugin.allocate<GenELF64DeviceImageTy>(); | |
| new (Image) GenELF64DeviceImageTy(ImageId, *this, TgtImage); | |
| // Create a temporary file. | |
| char TmpFileName[] = "/tmp/tmpfile_XXXXXX"; | |
| int TmpFileFd = mkstemp(TmpFileName); | |
| if (TmpFileFd == -1) | |
| return Plugin::error("Failed to create tmpfile for loading target image"); | |
| // Open the temporary file. | |
| FILE *TmpFile = fdopen(TmpFileFd, "wb"); | |
| if (!TmpFile) | |
| return Plugin::error("Failed to open tmpfile %s for loading target image", | |
| TmpFileName); | |
| // Write the image into the temporary file. | |
| size_t Written = fwrite(Image->getStart(), Image->getSize(), 1, TmpFile); | |
| if (Written != 1) | |
| return Plugin::error("Failed to write target image to tmpfile %s", | |
| TmpFileName); | |
| // Close the temporary file. | |
| int Ret = fclose(TmpFile); | |
| if (Ret) | |
| return Plugin::error("Failed to close tmpfile %s with the target image", | |
| TmpFileName); | |
| // Load the temporary file as a dynamic library. | |
| std::string ErrMsg; | |
| DynamicLibrary DynLib = | |
| DynamicLibrary::getPermanentLibrary(TmpFileName, &ErrMsg); | |
| // Check if the loaded library is valid. | |
| if (!DynLib.isValid()) | |
| return Plugin::error("Failed to load target image: %s", ErrMsg.c_str()); | |
| // Save a reference of the image's dynamic library. | |
| Image->setDynamicLibrary(DynLib); | |
| return Image; | |
| } | |
| /// Allocate memory. Use std::malloc in all cases. | |
| void *allocate(size_t Size, void *, TargetAllocTy Kind) override { | |
| if (Size == 0) | |
| return nullptr; | |
| void *MemAlloc = nullptr; | |
| switch (Kind) { | |
| case TARGET_ALLOC_DEFAULT: | |
| case TARGET_ALLOC_DEVICE: | |
| case TARGET_ALLOC_HOST: | |
| case TARGET_ALLOC_SHARED: | |
| case TARGET_ALLOC_DEVICE_NON_BLOCKING: | |
| MemAlloc = std::malloc(Size); | |
| break; | |
| } | |
| return MemAlloc; | |
| } | |
| /// Free the memory. Use std::free in all cases. | |
| int free(void *TgtPtr, TargetAllocTy Kind) override { | |
| std::free(TgtPtr); | |
| return OFFLOAD_SUCCESS; | |
| } | |
| /// This plugin does nothing to lock buffers. Do not return an error, just | |
| /// return the same pointer as the device pointer. | |
| Expected<void *> dataLockImpl(void *HstPtr, int64_t Size) override { | |
| return HstPtr; | |
| } | |
| /// Nothing to do when unlocking the buffer. | |
| Error dataUnlockImpl(void *HstPtr) override { return Plugin::success(); } | |
| /// Indicate that the buffer is not pinned. | |
| Expected<bool> isPinnedPtrImpl(void *HstPtr, void *&BaseHstPtr, | |
| void *&BaseDevAccessiblePtr, | |
| size_t &BaseSize) const override { | |
| return false; | |
| } | |
| /// Submit data to the device (host to device transfer). | |
| Error dataSubmitImpl(void *TgtPtr, const void *HstPtr, int64_t Size, | |
| AsyncInfoWrapperTy &AsyncInfoWrapper) override { | |
| std::memcpy(TgtPtr, HstPtr, Size); | |
| return Plugin::success(); | |
| } | |
| /// Retrieve data from the device (device to host transfer). | |
| Error dataRetrieveImpl(void *HstPtr, const void *TgtPtr, int64_t Size, | |
| AsyncInfoWrapperTy &AsyncInfoWrapper) override { | |
| std::memcpy(HstPtr, TgtPtr, Size); | |
| return Plugin::success(); | |
| } | |
| /// Exchange data between two devices within the plugin. This function is not | |
| /// supported in this plugin. | |
| Error dataExchangeImpl(const void *SrcPtr, GenericDeviceTy &DstGenericDevice, | |
| void *DstPtr, int64_t Size, | |
| AsyncInfoWrapperTy &AsyncInfoWrapper) override { | |
| // This function should never be called because the function | |
| // GenELF64PluginTy::isDataExchangable() returns false. | |
| return Plugin::error("dataExchangeImpl not supported"); | |
| } | |
| /// All functions are already synchronous. No need to do anything on this | |
| /// synchronization function. | |
| Error synchronizeImpl(__tgt_async_info &AsyncInfo) override { | |
| return Plugin::success(); | |
| } | |
| /// All functions are already synchronous. No need to do anything on this | |
| /// query function. | |
| Error queryAsyncImpl(__tgt_async_info &AsyncInfo) override { | |
| return Plugin::success(); | |
| } | |
| /// This plugin does not support interoperability | |
| Error initAsyncInfoImpl(AsyncInfoWrapperTy &AsyncInfoWrapper) override { | |
| return Plugin::error("initAsyncInfoImpl not supported"); | |
| } | |
| /// This plugin does not support interoperability | |
| Error initDeviceInfoImpl(__tgt_device_info *DeviceInfo) override { | |
| return Plugin::error("initDeviceInfoImpl not supported"); | |
| } | |
| /// This plugin does not support the event API. Do nothing without failing. | |
| Error createEventImpl(void **EventPtrStorage) override { | |
| *EventPtrStorage = nullptr; | |
| return Plugin::success(); | |
| } | |
| Error destroyEventImpl(void *EventPtr) override { return Plugin::success(); } | |
| Error recordEventImpl(void *EventPtr, | |
| AsyncInfoWrapperTy &AsyncInfoWrapper) override { | |
| return Plugin::success(); | |
| } | |
| Error waitEventImpl(void *EventPtr, | |
| AsyncInfoWrapperTy &AsyncInfoWrapper) override { | |
| return Plugin::success(); | |
| } | |
| Error syncEventImpl(void *EventPtr) override { return Plugin::success(); } | |
| /// Print information about the device. | |
| Error obtainInfoImpl(InfoQueueTy &Info) override { | |
| Info.add("Device Type", "Generic-elf-64bit"); | |
| return Plugin::success(); | |
| } | |
| /// This plugin should not setup the device environment or memory pool. | |
| virtual bool shouldSetupDeviceEnvironment() const override { return false; }; | |
| virtual bool shouldSetupDeviceMemoryPool() const override { return false; }; | |
| /// Getters and setters for stack size and heap size not relevant. | |
| Error getDeviceStackSize(uint64_t &Value) override { | |
| Value = 0; | |
| return Plugin::success(); | |
| } | |
| Error setDeviceStackSize(uint64_t Value) override { | |
| return Plugin::success(); | |
| } | |
| Error getDeviceHeapSize(uint64_t &Value) override { | |
| Value = 0; | |
| return Plugin::success(); | |
| } | |
| Error setDeviceHeapSize(uint64_t Value) override { return Plugin::success(); } | |
| private: | |
| /// Grid values for Generic ELF64 plugins. | |
| static constexpr GV GenELF64GridValues = { | |
| 1, // GV_Slot_Size | |
| 1, // GV_Warp_Size | |
| 1, // GV_Max_Teams | |
| 1, // GV_Default_Num_Teams | |
| 1, // GV_SimpleBufferSize | |
| 1, // GV_Max_WG_Size | |
| 1, // GV_Default_WG_Size | |
| }; | |
| }; | |
| class GenELF64GlobalHandlerTy final : public GenericGlobalHandlerTy { | |
| public: | |
| Error getGlobalMetadataFromDevice(GenericDeviceTy &GenericDevice, | |
| DeviceImageTy &Image, | |
| GlobalTy &DeviceGlobal) override { | |
| const char *GlobalName = DeviceGlobal.getName().data(); | |
| GenELF64DeviceImageTy &GenELF64Image = | |
| static_cast<GenELF64DeviceImageTy &>(Image); | |
| // Get dynamic library that has loaded the device image. | |
| DynamicLibrary &DynLib = GenELF64Image.getDynamicLibrary(); | |
| // Get the address of the symbol. | |
| void *Addr = DynLib.getAddressOfSymbol(GlobalName); | |
| if (Addr == nullptr) { | |
| return Plugin::error("Failed to load global '%s'", GlobalName); | |
| } | |
| // Save the pointer to the symbol. | |
| DeviceGlobal.setPtr(Addr); | |
| return Plugin::success(); | |
| } | |
| }; | |
| /// Class implementing the plugin functionalities for GenELF64. | |
| struct GenELF64PluginTy final : public GenericPluginTy { | |
| /// Create the GenELF64 plugin. | |
| GenELF64PluginTy() : GenericPluginTy(getTripleArch()) {} | |
| /// This class should not be copied. | |
| GenELF64PluginTy(const GenELF64PluginTy &) = delete; | |
| GenELF64PluginTy(GenELF64PluginTy &&) = delete; | |
| /// Initialize the plugin and return the number of devices. | |
| Expected<int32_t> initImpl() override { | |
| #ifdef USES_DYNAMIC_FFI | |
| if (auto Err = Plugin::check(ffi_init(), "Failed to initialize libffi")) | |
| return std::move(Err); | |
| #endif | |
| return NUM_DEVICES; | |
| } | |
| /// Deinitialize the plugin. | |
| Error deinitImpl() override { return Plugin::success(); } | |
| /// Creates a generic ELF device. | |
| GenericDeviceTy *createDevice(GenericPluginTy &Plugin, int32_t DeviceId, | |
| int32_t NumDevices) override { | |
| return new GenELF64DeviceTy(Plugin, DeviceId, NumDevices); | |
| } | |
| /// Creates a generic global handler. | |
| GenericGlobalHandlerTy *createGlobalHandler() override { | |
| return new GenELF64GlobalHandlerTy(); | |
| } | |
| /// Get the ELF code to recognize the compatible binary images. | |
| uint16_t getMagicElfBits() const override { | |
| return utils::elf::getTargetMachine(); | |
| } | |
| /// This plugin does not support exchanging data between two devices. | |
| bool isDataExchangable(int32_t SrcDeviceId, int32_t DstDeviceId) override { | |
| return false; | |
| } | |
| /// All images (ELF-compatible) should be compatible with this plugin. | |
| Expected<bool> isELFCompatible(uint32_t, StringRef) const override { | |
| return true; | |
| } | |
| Triple::ArchType getTripleArch() const override { | |
| #if defined(__x86_64__) | |
| return llvm::Triple::x86_64; | |
| #elif defined(__s390x__) | |
| return llvm::Triple::systemz; | |
| #elif defined(__aarch64__) | |
| #ifdef LITTLEENDIAN_CPU | |
| return llvm::Triple::aarch64; | |
| #else | |
| return llvm::Triple::aarch64_be; | |
| #endif | |
| #elif defined(__powerpc64__) | |
| #ifdef LITTLEENDIAN_CPU | |
| return llvm::Triple::ppc64le; | |
| #else | |
| return llvm::Triple::ppc64; | |
| #endif | |
| #elif defined(__riscv) && (__riscv_xlen == 64) | |
| return llvm::Triple::riscv64; | |
| #elif defined(__loongarch__) && (__loongarch_grlen == 64) | |
| return llvm::Triple::loongarch64; | |
| #else | |
| return llvm::Triple::UnknownArch; | |
| #endif | |
| } | |
| const char *getName() const override { return GETNAME(TARGET_NAME); } | |
| }; | |
| template <typename... ArgsTy> | |
| static Error Plugin::check(int32_t Code, const char *ErrMsg, ArgsTy... Args) { | |
| if (Code == 0) | |
| return Error::success(); | |
| return createStringError<ArgsTy..., const char *>( | |
| inconvertibleErrorCode(), ErrMsg, Args..., std::to_string(Code).data()); | |
| } | |
| } // namespace plugin | |
| } // namespace target | |
| } // namespace omp | |
| } // namespace llvm | |
| extern "C" { | |
| llvm::omp::target::plugin::GenericPluginTy *createPlugin_host() { | |
| return new llvm::omp::target::plugin::GenELF64PluginTy(); | |
| } | |
| } |