diff --git a/CMakeLists.txt b/CMakeLists.txt index 5561a66dce..f644181c54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,7 @@ option(UR_BUILD_ADAPTER_L0 "build level 0 adapter from SYCL" OFF) option(UR_BUILD_ADAPTER_OPENCL "build opencl adapter from SYCL" OFF) option(UR_BUILD_ADAPTER_CUDA "build cuda adapter from SYCL" OFF) option(UR_BUILD_ADAPTER_HIP "build hip adapter from SYCL" OFF) +option(UR_BUILD_EXAMPLE_CODEGEN "Build the codegen example." OFF) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index cee4bdab05..aca7f396c2 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -6,6 +6,9 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) add_subdirectory(hello_world) +if(UR_BUILD_EXAMPLE_CODEGEN) + add_subdirectory(codegen) +endif() if(UR_ENABLE_TRACING) add_subdirectory(collector) endif() diff --git a/examples/codegen/CMakeLists.txt b/examples/codegen/CMakeLists.txt new file mode 100644 index 0000000000..ddaabcc34f --- /dev/null +++ b/examples/codegen/CMakeLists.txt @@ -0,0 +1,47 @@ +# Copyright (C) 2022 Intel Corporation +# Part of the Unified-Runtime Project, under the Apache License v2.0 with LLVM Exceptions. +# See LICENSE.TXT +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +set(TARGET_NAME codegen) + +find_package(LLVM CONFIG) +find_package(PkgConfig) + +set(TRANSLATOR_FOUND "FALSE") +if(${PkgConfig_FOUND}) + pkg_check_modules(LLVMSPIRVLib IMPORTED_TARGET LLVMSPIRVLib) +endif() + +if(LLVM_FOUND AND PkgConfig_FOUND AND LLVMSPIRVLib_FOUND) + message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") + message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") + llvm_map_components_to_libnames(llvm_libs support core irreader bitwriter) + + add_ur_executable(${TARGET_NAME} + ${CMAKE_CURRENT_SOURCE_DIR}/codegen.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/helpers.cpp + ) + + target_include_directories(${TARGET_NAME} PRIVATE ${LLVM_INCLUDE_DIRS}) + target_compile_definitions(${TARGET_NAME} PRIVATE ${LLVM_DEFINITIONS}) + target_link_libraries(${TARGET_NAME} + PRIVATE + ${CMAKE_DL_LIBS} + ${PROJECT_NAME}::headers + ${PROJECT_NAME}::loader + LLVM + PkgConfig::LLVMSPIRVLib + ) + # TODO: Depend on building adapters. + + if(MSVC) + set_target_properties(${TARGET_NAME} + PROPERTIES + VS_DEBUGGER_COMMAND_ARGUMENTS "" + VS_DEBUGGER_WORKING_DIRECTORY "$(OutDir)" + ) + endif() +else() + message(STATUS "The environment did not satisfy dependency requirements (LLVM, PkgConfig, LLVMSPIRVLib) for codegen example (skipping target).") +endif() diff --git a/examples/codegen/codegen.cpp b/examples/codegen/codegen.cpp new file mode 100644 index 0000000000..0352ae6e02 --- /dev/null +++ b/examples/codegen/codegen.cpp @@ -0,0 +1,140 @@ +/* + * + * Copyright (C) 2023 Intel Corporation + * + * Part of the Unified-Runtime Project, under the Apache License v2.0 with LLVM Exceptions. + * See LICENSE.TXT + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + * + * @file codegen.cpp + * + * @brief UR code generation and execution example for use with the Level Zero adapter. + * + * The codegen example demonstrates a complete flow for generating LLVM IR, + * translating it to SPIR-V, and submitting the kernel to Level Zero Runtime via UR API. + */ + +#include +#include + +#include "helpers.h" +#include "ur_api.h" + +void ur_check(const ur_result_t r) { + if (r != UR_RESULT_SUCCESS) { + urTearDown(nullptr); + throw std::runtime_error("Unified runtime error: " + std::to_string(r)); + } +} + +std::vector get_adapters() { + uint32_t adapterCount = 0; + ur_check(urAdapterGet(0, nullptr, &adapterCount)); + + if (!adapterCount) { + throw std::runtime_error("No adapters available."); + } + + std::vector adapters(adapterCount); + ur_check(urAdapterGet(adapterCount, adapters.data(), nullptr)); + return adapters; +} + +std::vector +get_platforms(std::vector &adapters) { + uint32_t platformCount = 0; + ur_check(urPlatformGet(adapters.data(), adapters.size(), 1, nullptr, + &platformCount)); + + if (!platformCount) { + throw std::runtime_error("No platforms available."); + } + + std::vector platforms(platformCount); + ur_check(urPlatformGet(adapters.data(), adapters.size(), platformCount, + platforms.data(), nullptr)); + return platforms; +} + +std::vector get_gpus(ur_platform_handle_t p) { + uint32_t deviceCount = 0; + ur_check(urDeviceGet(p, UR_DEVICE_TYPE_GPU, 0, nullptr, &deviceCount)); + + if (!deviceCount) { + throw std::runtime_error("No GPUs available."); + } + + std::vector devices(deviceCount); + ur_check(urDeviceGet(p, UR_DEVICE_TYPE_GPU, deviceCount, devices.data(), + nullptr)); + return devices; +} + +template struct alignas(4096) AlignedArray { + T data[N]; +}; + +int main() { + ur_loader_config_handle_t loader_config = nullptr; + ur_check(urInit(UR_DEVICE_INIT_FLAG_GPU, loader_config)); + + auto adapters = get_adapters(); + auto platforms = get_platforms(adapters); + auto gpus = get_gpus(platforms.front()); + auto spv = generate_plus_one_spv(); + + constexpr int a_size = 32; + AlignedArray a, b; + for (auto i = 0; i < a_size; ++i) { + a.data[i] = a_size - i; + b.data[i] = i; + } + + auto current_device = gpus.front(); + + ur_context_handle_t hContext; + ur_check(urContextCreate(1, ¤t_device, nullptr, &hContext)); + + ur_program_handle_t hProgram; + ur_check(urProgramCreateWithIL(hContext, spv.data(), spv.size(), nullptr, + &hProgram)); + ur_check(urProgramBuild(hContext, hProgram, nullptr)); + + ur_mem_handle_t dA, dB; + ur_check(urMemBufferCreate(hContext, UR_MEM_FLAG_READ_WRITE, + a_size * sizeof(int), nullptr, &dA)); + ur_check(urMemBufferCreate(hContext, UR_MEM_FLAG_READ_WRITE, + a_size * sizeof(int), nullptr, &dB)); + + ur_kernel_handle_t hKernel; + ur_check(urKernelCreate(hProgram, "plus1", &hKernel)); + ur_check(urKernelSetArgMemObj(hKernel, 0, nullptr, dA)); + ur_check(urKernelSetArgMemObj(hKernel, 1, nullptr, dB)); + + ur_queue_handle_t queue; + ur_check(urQueueCreate(hContext, current_device, nullptr, &queue)); + + ur_check(urEnqueueMemBufferWrite(queue, dA, true, 0, a_size * sizeof(int), + a.data, 0, nullptr, nullptr)); + ur_check(urEnqueueMemBufferWrite(queue, dB, true, 0, a_size * sizeof(int), + b.data, 0, nullptr, nullptr)); + + const size_t gWorkOffset[] = {0, 0, 0}; + const size_t gWorkSize[] = {128, 1, 1}; + const size_t lWorkSize[] = {1, 1, 1}; + ur_event_handle_t event; + ur_check(urEnqueueKernelLaunch(queue, hKernel, 3, gWorkOffset, gWorkSize, + lWorkSize, 0, nullptr, &event)); + + ur_check(urEnqueueMemBufferRead(queue, dB, true, 0, a_size * sizeof(int), + b.data, 1, &event, nullptr)); + + ur_check(urQueueFinish(queue)); + + for (int i = 0; i < a_size; ++i) { + std::cout << b.data[i] << " "; + } + std::cout << std::endl; + + return urTearDown(nullptr) == UR_RESULT_SUCCESS ? 0 : 1; +} diff --git a/examples/codegen/helpers.cpp b/examples/codegen/helpers.cpp new file mode 100644 index 0000000000..9c7ae73637 --- /dev/null +++ b/examples/codegen/helpers.cpp @@ -0,0 +1,103 @@ +/* + * + * Copyright (C) 2023 Intel Corporation + * + * Part of the Unified-Runtime Project, under the Apache License v2.0 with LLVM Exceptions. + * See LICENSE.TXT + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include "helpers.h" + +#include "llvm/IR/Function.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/Module.h" +#include "llvm/Support/raw_ostream.h" +#include + +#include + +#include + +std::string generate_plus_one_spv() { + using namespace llvm; + LLVMContext ctx; + std::unique_ptr module = + std::make_unique("code_generated", ctx); + module->setTargetTriple("spir64-unknown-unknown"); + IRBuilder<> builder(ctx); + + std::vector args{Type::getInt32PtrTy(ctx, 1), + Type::getInt32PtrTy(ctx, 1)}; + FunctionType *f_type = FunctionType::get(Type::getVoidTy(ctx), args, false); + Function *f = + Function::Create(f_type, GlobalValue::LinkageTypes::ExternalLinkage, + "plus1", module.get()); + f->setCallingConv(CallingConv::SPIR_KERNEL); + + // get_global_id + FunctionType *ggi_type = FunctionType::get(Type::getInt32Ty(ctx), + {Type::getInt32Ty(ctx)}, false); + Function *get_global_idj = + Function::Create(ggi_type, GlobalValue::LinkageTypes::ExternalLinkage, + "_Z13get_global_idj", module.get()); + get_global_idj->setCallingConv(CallingConv::SPIR_FUNC); + + BasicBlock *entry = BasicBlock::Create(ctx, "entry", f); + + builder.SetInsertPoint(entry); + Constant *zero = ConstantInt::get(Type::getInt32Ty(ctx), 0); + Constant *onei = ConstantInt::get(Type::getInt32Ty(ctx), 1); + Value *idx = builder.CreateCall(get_global_idj, zero, "idx"); + auto argit = f->args().begin(); +#if LLVM_VERSION_MAJOR > 15 + Value *firstElemSrc = + builder.CreateGEP(argit->getType(), argit, idx, "src.idx"); + ++argit; + Value *firstElemDst = + builder.CreateGEP(argit->getType(), argit, idx, "dst.idx"); +#elif LLVM_VERSION_MAJOR > 12 + Value *firstElemSrc = builder.CreateGEP( + argit->getType()->getPointerElementType(), argit, idx, "src.idx"); + ++argit; + Value *firstElemDst = builder.CreateGEP( + argit->getType()->getPointerElementType(), argit, idx, "dst.idx"); +#else + Value *firstElemSrc = builder.CreateGEP(f->args().begin(), idx, "src.idx"); + Value *firstElemDst = builder.CreateGEP(++argit, idx, "dst.idx"); +#endif + Value *ldSrc = + builder.CreateLoad(Type::getInt32Ty(ctx), firstElemSrc, "ld"); + Value *result = builder.CreateAdd(ldSrc, onei, "foo"); + builder.CreateStore(result, firstElemDst); + builder.CreateRetVoid(); + + // set metadata -- pretend we're opencl (see + // https://github.com/KhronosGroup/SPIRV-LLVM-Translator/blob/master/docs/SPIRVRepresentationInLLVM.rst#spir-v-instructions-mapped-to-llvm-metadata) + Metadata *spirv_src_ops[] = { + ConstantAsMetadata::get( + ConstantInt::get(Type::getInt32Ty(ctx), 3 /*OpenCL_C*/)), + ConstantAsMetadata::get(ConstantInt::get(Type::getInt32Ty(ctx), + 102000 /*OpenCL ver 1.2*/))}; + NamedMDNode *spirv_src = module->getOrInsertNamedMetadata("spirv.Source"); + spirv_src->addOperand(MDNode::get(ctx, spirv_src_ops)); + + module->print(errs(), nullptr); + + SPIRV::TranslatorOpts opts; + opts.enableAllExtensions(); + opts.setDesiredBIsRepresentation(SPIRV::BIsRepresentation::OpenCL12); + opts.setDebugInfoEIS(SPIRV::DebugInfoEIS::OpenCL_DebugInfo_100); + + std::ostringstream ss; + std::string err; + auto success = writeSpirv(module.get(), opts, ss, err); + if (!success) { + errs() << "Spirv translation failed with error: " << err << "\n"; + } else { + errs() << "Spirv tranlsation success.\n"; + } + errs() << "Code size: " << ss.str().size() << "\n"; + + return ss.str(); +} diff --git a/examples/codegen/helpers.h b/examples/codegen/helpers.h new file mode 100644 index 0000000000..aca76ae134 --- /dev/null +++ b/examples/codegen/helpers.h @@ -0,0 +1,14 @@ +/* + * + * Copyright (C) 2023 Intel Corporation + * + * Part of the Unified-Runtime Project, under the Apache License v2.0 with LLVM Exceptions. + * See LICENSE.TXT + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#pragma once + +#include + +std::string generate_plus_one_spv(); diff --git a/scripts/deps.yml b/scripts/deps.yml new file mode 100644 index 0000000000..8c5f5d068b --- /dev/null +++ b/scripts/deps.yml @@ -0,0 +1,37 @@ +name: examples +channels: + - conda-forge +dependencies: + - _libgcc_mutex=0.1 + - _openmp_mutex=4.5 + - bzip2=1.0.8 + - c-ares=1.19.1 + - ca-certificates=2023.5.7 + - cmake=3.26.4 + - expat=2.5.0 + - keyutils=1.6.1 + - krb5=1.20.1 + - level-zero=1.11.0 + - level-zero-devel=1.11.0 + - libcurl=8.1.2 + - libedit=3.1.20191231 + - libev=4.33 + - libexpat=2.5.0 + - libgcc-ng=13.1.0 + - libgomp=13.1.0 + - libllvm14=14.0.6 + - libnghttp2=1.52.0 + - libssh2=1.11.0 + - libstdcxx-ng=13.1.0 + - libuv=1.44.2 + - libzlib=1.2.13 + - llvm-spirv=14.0.0 + - llvm-tools=14.0.6 + - llvmdev=14.0.6 + - ncurses=6.4 + - openssl=3.1.1 + - pkg-config=0.29.2 + - rhash=1.4.3 + - xz=5.2.6 + - zlib=1.2.13 + - zstd=1.5.2