| // Copyright 2021 The IREE Authors | |
| // | |
| // Licensed 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 | |
| #include "compiler/plugins/target/LLVMCPU/Builtins/Device.h" | |
| #include "iree/builtins/device/libdevice_bitcode.h" | |
| #include "llvm/Bitcode/BitcodeReader.h" | |
| #include "llvm/IR/Constants.h" | |
| #include "llvm/Support/MemoryBufferRef.h" | |
| #include "mlir/Support/LLVM.h" | |
| namespace mlir::iree_compiler::IREE::HAL { | |
| static const iree_file_toc_t *lookupDeviceFile(StringRef filename) { | |
| for (size_t i = 0; i < iree_builtins_libdevice_bitcode_size(); ++i) { | |
| const auto &file_toc = iree_builtins_libdevice_bitcode_create()[i]; | |
| if (filename == file_toc.name) | |
| return &file_toc; | |
| } | |
| return nullptr; | |
| } | |
| static const iree_file_toc_t * | |
| lookupDeviceFile(llvm::TargetMachine *targetMachine) { | |
| const auto &triple = targetMachine->getTargetTriple(); | |
| // NOTE: other arch-specific checks go here. | |
| if (triple.isWasm()) { | |
| // TODO(benvanik): feature detect simd and such. | |
| // auto features = targetMachine->getTargetFeatureString(); | |
| if (triple.isArch32Bit()) { | |
| return lookupDeviceFile("libdevice_wasm32_generic.bc"); | |
| } else if (triple.isArch64Bit()) { | |
| return lookupDeviceFile("libdevice_wasm64_generic.bc"); | |
| } | |
| } | |
| // Fallback path using the generic wasm variants as they are largely | |
| // machine-agnostic. | |
| if (triple.isArch32Bit()) { | |
| return lookupDeviceFile("libdevice_wasm32_generic.bc"); | |
| } else if (triple.isArch64Bit() && | |
| targetMachine->getTargetFeatureString().contains("+sme")) { | |
| return lookupDeviceFile("libdevice_aarch64_sme.bc"); | |
| } else if (triple.isArch64Bit()) { | |
| return lookupDeviceFile("libdevice_wasm64_generic.bc"); | |
| } else { | |
| return nullptr; | |
| } | |
| } | |
| llvm::Expected<std::unique_ptr<llvm::Module>> | |
| loadDeviceBitcode(llvm::TargetMachine *targetMachine, | |
| llvm::LLVMContext &context) { | |
| // Find a bitcode file for the current architecture. | |
| const auto *file = lookupDeviceFile(targetMachine); | |
| if (!file) { | |
| return llvm::createStringError(llvm::inconvertibleErrorCode(), | |
| "no matching architecture bitcode file"); | |
| } | |
| // Load the generic bitcode file contents. | |
| llvm::MemoryBufferRef bitcodeBufferRef( | |
| llvm::StringRef(file->data, file->size), file->name); | |
| auto bitcodeModuleValue = llvm::parseBitcodeFile(bitcodeBufferRef, context); | |
| if (!bitcodeModuleValue) | |
| return bitcodeModuleValue; | |
| auto bitcodeModule = std::move(bitcodeModuleValue.get()); | |
| // Clang adds its own per-function attributes that we need to strip so that | |
| // our current executable variant target is used instead. | |
| for (auto &func : bitcodeModule->functions()) { | |
| func.removeFnAttr("target-cpu"); | |
| func.removeFnAttr("tune-cpu"); | |
| func.removeFnAttr("target-features"); | |
| } | |
| return std::move(bitcodeModule); | |
| } | |
| static void overridePlatformGlobal(llvm::Module &module, StringRef globalName, | |
| uint32_t newValue) { | |
| // NOTE: the global will not be defined if it is not used in the module. | |
| auto *globalValue = module.getNamedGlobal(globalName); | |
| if (!globalValue) | |
| return; | |
| globalValue->setLinkage(llvm::GlobalValue::PrivateLinkage); | |
| globalValue->setDSOLocal(true); | |
| globalValue->setConstant(true); | |
| globalValue->setInitializer( | |
| llvm::ConstantInt::get(globalValue->getValueType(), APInt(32, newValue))); | |
| } | |
| void specializeDeviceModule(IREE::HAL::ExecutableVariantOp variantOp, | |
| llvm::Module &module, | |
| llvm::TargetMachine &targetMachine) { | |
| overridePlatformGlobal(module, "libdevice_platform_example_flag", 0u); | |
| } | |
| } // namespace mlir::iree_compiler::IREE::HAL |