// Copyright (c) 2021 CINN Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "paddle/cinn/backends/llvm/execution_engine.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // NOLINT #include #include #include #include "paddle/cinn/backends/codegen_cuda_host.h" #include "paddle/cinn/backends/llvm/cinn_runtime_llvm_ir.h" #include "paddle/cinn/backends/llvm/codegen_llvm.h" #include "paddle/cinn/backends/llvm/codegen_x86.h" #include "paddle/cinn/backends/llvm/llvm_optimizer.h" #include "paddle/cinn/backends/llvm/llvm_util.h" #include "paddle/cinn/backends/llvm/runtime_symbol_registry.h" #include "paddle/cinn/common/target.h" #include "paddle/cinn/ir/ir_printer.h" #include "paddle/cinn/runtime/arch_device.h" #include "paddle/cinn/runtime/intrinsic.h" #include "paddle/cinn/utils/profiler.h" COMMON_DECLARE_bool(enable_cinn_kernel_cache); COMMON_DECLARE_string(cinn_kernel_cache_save_path); namespace cinn::backends { namespace { void InitializeLLVMPasses() { llvm::InitializeNativeTarget(); llvm::InitializeNativeTargetAsmPrinter(); auto ®istry = *llvm::PassRegistry::getPassRegistry(); llvm::initializeCore(registry); llvm::initializeTransformUtils(registry); llvm::initializeScalarOpts(registry); llvm::initializeIPO(registry); llvm::initializeInstCombine(registry); llvm::initializeAggressiveInstCombine(registry); llvm::initializeAnalysis(registry); llvm::initializeVectorization(registry); llvm::initializeSROALegacyPassPass(registry); // llvm::initializeCodeGen(registry); // llvm::initializeTarget(registry); // llvm::initializeCodeGenPreparePass(registry); } } // namespace void NaiveObjectCache::notifyObjectCompiled(const llvm::Module *m, llvm::MemoryBufferRef obj_buffer) { cached_objects_[m->getModuleIdentifier()] = llvm::MemoryBuffer::getMemBufferCopy(obj_buffer.getBuffer(), obj_buffer.getBufferIdentifier()); } std::unique_ptr NaiveObjectCache::getObject( const llvm::Module *m) { auto it = cached_objects_.find(m->getModuleIdentifier()); if (it == cached_objects_.end()) { VLOG(1) << "No object for " << m->getModuleIdentifier() << " in cache. Compiling."; return nullptr; } VLOG(3) << "Object for " << m->getModuleIdentifier() << " loaded from cache."; return llvm::MemoryBuffer::getMemBuffer(it->second->getMemBufferRef()); } /*static*/ std::unique_ptr ExecutionEngine::Create( const ExecutionOptions &config) { VLOG(6) << "===================== Create CINN ExecutionEngine begin " "===================="; VLOG(6) << "initialize llvm config"; VLOG(6) << "llvm version: " << LLVM_VERSION_STRING; VLOG(6) << "llvm default target triple: " << LLVM_DEFAULT_TARGET_TRIPLE; static std::once_flag flag; std::call_once(flag, InitializeLLVMPasses); auto engine = std::make_unique(/*enable_object_cache=*/true); auto compile_layer_creator = [&engine](llvm::orc::JITTargetMachineBuilder jtmb) -> llvm::Expected< std::unique_ptr> { auto machine = llvm::cantFail(jtmb.createTargetMachine()); VLOG(6) << "create llvm compile layer"; VLOG(6) << "Target Name: " << machine->getTarget().getName(); VLOG(6) << "Target CPU: " << machine->getTargetCPU().str() << std::endl; return std::make_unique( std::move(machine), engine->cache_.get()); }; auto object_layer_creator = [&](llvm::orc::ExecutionSession &session, const llvm::Triple &triple) { auto object_layer = std::make_unique( session, []() { return std::make_unique(); }); llvm::orc::JITDylib *main_jd = session.getJITDylibByName("
"); if (!main_jd) { main_jd = &llvm::cantFail(session.createJITDylib("
")); } return object_layer; }; VLOG(6) << "create jit execution engine"; engine->jit_ = llvm::cantFail(llvm::orc::LLJITBuilder() .setCompileFunctionCreator(compile_layer_creator) .setObjectLinkingLayerCreator(object_layer_creator) .create()); engine->jit_->getMainJITDylib().addGenerator(llvm::cantFail( llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess( engine->jit_->getDataLayout().getGlobalPrefix()))); VLOG(6) << "register global runtime call symbols"; engine->RegisterGlobalRuntimeSymbols(); VLOG(6) << "===================== Create CINN ExecutionEngine end " "===================="; engine->ctx = std::make_unique(); engine->b = std::make_unique>(*engine->ctx); llvm::SMDiagnostic error; engine->m = llvm::parseAssemblyString( AsStringRef(backends::kRuntimeLlvmIr), error, *engine->ctx); return engine; } template void ExecutionEngine::Link(const ir::Module &module) { if (module.functions().size() == 0) { return; } utils::RecordEvent("ExecutionEngine Link", utils::EventType::kOrdinary); auto ir_emitter = std::make_unique(m.get(), b.get()); VLOG(3) << "ir_emitter->Compile(module) Begin"; ir_emitter->Compile(module); VLOG(3) << "ir_emitter->Compile(module) Succeed!"; PADDLE_ENFORCE_EQ( !llvm::verifyModule(*m, &llvm::errs()), true, ::common::errors::InvalidArgument("Sorry,Invalid module found")); auto machine = std::move(llvm::cantFail( llvm::cantFail(llvm::orc::JITTargetMachineBuilder::detectHost()) .createTargetMachine())); LLVMModuleOptimizer optimize(machine.get(), 3, {}, true); optimize(m.get()); PADDLE_ENFORCE_EQ( !llvm::verifyModule(*m, &llvm::errs()), true, ::common::errors::InvalidArgument("Invalid optimized module detected")); for (auto &f : *m) { VLOG(5) << "function: " << DumpToString(f); } llvm::raw_svector_ostream rawstream(buffer_); llvm::legacy::PassManager pass_manager; machine->addPassesToEmitFile( pass_manager, rawstream, nullptr, llvm::CGFT_ObjectFile); pass_manager.run(*m); if (VLOG_IS_ON(5)) { VLOG(5) << "======= dump jit execution session ======"; std::string buffer; llvm::raw_string_ostream os(buffer); decltype(auto) es = jit_->getExecutionSession(); es.dump(os); os.flush(); VLOG(5) << buffer; } } template <> void ExecutionEngine::Link(const ir::Module &module) { if (module.functions().size() == 0) { return; } utils::RecordEvent("ExecutionEngine Link", utils::EventType::kOrdinary); auto ir_emitter = std::make_unique(m.get(), b.get()); ir_emitter->Compile(module); } std::string GetDeviceId() { const auto device_id = cinn::runtime::GetArchDevice(common::DefaultDeviceTarget()); return std::to_string(device_id.value()); } // Use LLVM C++ API to compile .ll file to .o file bool ExecutionEngine::compileLLVMIR(llvm::Module *module, std::string output_path) { std::error_code EC; // 1. Find target for current platform std::string Error; const llvm::Target *TheTarget = llvm::TargetRegistry::lookupTarget(module->getTargetTriple(), Error); if (!TheTarget) { llvm::errs() << Error; return false; } // 2. Create TargetMachine (this is the core) llvm::TargetOptions TargetOpts; // **Core:** Must be set to PIC (Position Independent Code) llvm::Reloc::Model RelocModel = llvm::Reloc::Model::PIC_; std::string CPU = "generic"; std::string Features = ""; llvm::TargetMachine *TM = TheTarget->createTargetMachine( module->getTargetTriple(), CPU, Features, TargetOpts, RelocModel); module->setDataLayout(TM->createDataLayout()); module->setTargetTriple(TM->getTargetTriple().str()); // Remove dso_local for stderr for (llvm::GlobalVariable &GV : module->globals()) { if (GV.getName() == "stderr" || GV.isDeclaration()) { GV.setDSOLocal(false); } } // 3. Set output file path and type llvm::sys::fs::create_directories(output_path); std::string output_file = output_path + "/" + CINN_HOST_MODULE_OBJ; llvm::raw_fd_ostream dest(output_file, EC, llvm::sys::fs::OF_None); // 4. Create PassManager and add "Emit Object File" Pass llvm::legacy::PassManager pass_manager; llvm::CodeGenFileType FileType = llvm::CodeGenFileType::CGFT_ObjectFile; TM->addPassesToEmitFile(pass_manager, dest, nullptr, FileType); // 5. Run Pass to generate .o file! pass_manager.run(*module); dest.flush(); VLOG(5) << "LLVM API: Successfully compiled to '" << output_file; return true; } bool ExecutionEngine::linkSharedLibrary( const std::string output_path, const std::vector &cinn_runtime_include_path) { #ifdef CINN_WITH_CUDA llvm::sys::fs::create_directories(output_path); std::string output_so = output_path + "/" + CINN_CACHE_SO; std::string cuda_obj = output_path + "/" + CINN_CUDA_KERNEL_OBJ; std::string llvm_obj = output_path + "/" + CINN_HOST_MODULE_OBJ; std::string cuda_lib_path = CUDA_TOOLKIT_ROOT_DIR; std::string link_cmd = "g++ -shared -o " + output_so + " " + cuda_obj + " " + llvm_obj + " -L" + cuda_lib_path + "/lib64" + " -lcudart"; for (auto &header : cinn_runtime_include_path) { link_cmd += " -L " + header + " -lcinnapi"; } VLOG(5) << "Linker command: " << link_cmd << "\n"; int link_ret = system(link_cmd.c_str()); if (link_ret != 0) { std::cerr << "Error: Final linking failed.\n"; return false; } return true; #else CINN_NOT_IMPLEMENTED; #endif // CINN_WITH_CUDA } bool ExecutionEngine::AddModule( std::unique_ptr module, std::unique_ptr context, const size_t fusionHash, const std::vector &cinn_runtime_include_path) { utils::RecordEvent("ExecutionEngine AddModule", utils::EventType::kOrdinary); module->setDataLayout(jit_->getDataLayout()); if (VLOG_IS_ON(5)) { VLOG(5) << "======= dump jit lib =========="; std::string buffer; llvm::raw_string_ostream os(buffer); module->print(os, {}); // main_jd_->dump(os); os.flush(); VLOG(5) << buffer; } if (FLAGS_enable_cinn_kernel_cache) { std::error_code EC; std::string source_hash = std::to_string(fusionHash); std::string output_path = FLAGS_cinn_kernel_cache_save_path + "/" + GetDeviceId() + "/" + source_hash; llvm::sys::fs::create_directories(output_path); llvm::raw_fd_ostream out(output_path + "/" + CINN_HOST_MODULE_LLVM, EC); if (EC) { LOG(ERROR) << "Failed to open file: " << EC.message(); return false; } module->print(out, {}); out.close(); VLOG(5) << "LLVM IR dumped to " << CINN_HOST_MODULE_LLVM; std::string cache_so_path = output_path + "/" + CINN_CACHE_SO; if (std::ifstream(cache_so_path).good()) { // Cache file already exists, do nothing // module will register through LoadAndRegisterFromCache return true; } else { // Compiling LLVM IR with LLVM API if (!compileLLVMIR(module.get(), output_path)) { std::cerr << "Error: LLVM IR compilation failed.\n"; return false; } // Linking object files into shared library if (!linkSharedLibrary(output_path, cinn_runtime_include_path)) { std::cerr << "Error: Linking object files into shared library failed.\n"; return false; } } } llvm::orc::ThreadSafeContext tsc(std::move(context)); llvm::orc::ThreadSafeModule tsm(std::move(module), std::move(tsc)); llvm::cantFail(jit_->addIRModule(std::move(tsm))); return true; } void ExecutionEngine::RegisterModuleRuntimeSymbols( RuntimeSymbols &&module_symbols) { module_symbols_ = std::forward(module_symbols); auto *session = &jit_->getExecutionSession(); for (const auto &sym : module_symbols_.All()) { VLOG(3) << "Add symbol: {" << sym.first << ":" << sym.second << "}"; llvm::cantFail(jit_->getMainJITDylib().define(llvm::orc::absoluteSymbols( {{session->intern(sym.first), {llvm::pointerToJITTargetAddress(sym.second), llvm::JITSymbolFlags::Exported}}}))); } } bool ExecutionEngine::AddSelfModule( const size_t fusionHash, const std::vector &cinn_runtime_include_path) { return AddModule( std::move(m), std::move(ctx), fusionHash, cinn_runtime_include_path); } void ExecutionEngine::ExportObject(const std::string &path) { FILE *of = fopen(path.c_str(), "w"); fwrite(buffer_.data(), 1, buffer_.size(), of); fclose(of); } void *ExecutionEngine::Lookup(std::string_view name) { utils::RecordEvent("ExecutionEngine Lookup", utils::EventType::kOrdinary); std::lock_guard lock(mu_); if (auto symbol = jit_->lookup(AsStringRef(name))) { return reinterpret_cast(symbol->getAddress()); } LOG(ERROR) << "Unknown symbol name[" << name << "]"; return nullptr; } void ExecutionEngine::RegisterGlobalRuntimeSymbols() { utils::RecordEvent("ExecutionEngine RegisterGlobalRuntimeSymbols", utils::EventType::kOrdinary); const auto ®istry = GlobalSymbolRegistry::Global(); auto *session = &jit_->getExecutionSession(); for (const auto &sym : registry.All()) { llvm::cantFail(jit_->getMainJITDylib().define(llvm::orc::absoluteSymbols( {{session->intern(sym.first), {llvm::pointerToJITTargetAddress(sym.second), llvm::JITSymbolFlags::None}}}))); } } template void ExecutionEngine::Link(const ir::Module &module); template void ExecutionEngine::Link(const ir::Module &module); template void ExecutionEngine::Link(const ir::Module &module); template void ExecutionEngine::Link( const ir::Module &module); } // namespace cinn::backends