[flang-rt] Implement basic support for I/O from OpenMP GPU Offloading (#181039)

Summary:
This PR provides the minimal support for Fortran I/O coming from a GPU
in OpenMP offloading. We use the same support the `libc` uses for its
printing through the RPC server. The helper functions `rpc::dispatch`
and `rpc::invoke` help make this mostly automatic.

Becaus Fortran I/O is not reentrant, the vast majority of complexity
comes from needing to stitch together calls from the GPU until they can
be executed all at once. This is needed not only because of the
limitations of recursive I/O, but without this the output would all be
interleaved because of the GPU's lock-step execution.

As such, the return values from the intermediate functions are
meaningless, all returning true. The final value is correct however. For
cookies we create a context pointer on the server to chain these
together.

Works on both my AMD and NVIDIA GPUs.
```fortran
program hello_gpu
  implicit none

  !$omp target teams num_teams(1)
  !$omp parallel num_threads(2)
    ! Print strings
    print *, "Hello from GPU"
  !$omp end parallel
  !$omp end target teams

end program hello_gpu
```
```console
> flang hello.f90 -O2 -fopenmp --offload-arch=gfx1030 
> ./a.out 
 Hello from GPU
 Hello from GPU
> flang hello.f90 -O2 -fopenmp --offload-arch=sm_89  
> ./a.out 
 Hello from GPU
 Hello from GPU
```
This commit is contained in:
Joseph Huber 2026-02-20 07:56:59 -06:00 committed by GitHub
parent 78e19e9672
commit 21b3461440
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 531 additions and 6 deletions

View file

@ -57,6 +57,8 @@ if (CMAKE_VERSION VERSION_LESS "3.24")
endif ()
enable_language(Fortran)
# Try to include the RPC utilities from the `libc` project for GPU I/O support.
include(FindLibcCommonUtils)
list(APPEND CMAKE_MODULE_PATH
"${FLANG_RT_SOURCE_DIR}/cmake/modules"

View file

@ -122,6 +122,11 @@ function (add_flangrt_library name)
list(APPEND extra_args EXCLUDE_FROM_ALL)
endif ()
# Include the RPC utilities from the `libc` project.
if (TARGET llvm-libc-common-utilities)
set(extra_deps llvm-libc-common-utilities)
endif()
# Also add header files to IDEs to list as part of the library.
set_source_files_properties(${ARG_ADDITIONAL_HEADERS} PROPERTIES HEADER_FILE_ONLY ON)
@ -139,11 +144,11 @@ function (add_flangrt_library name)
endif ()
if (build_static)
add_library("${name_static}" STATIC ${extra_args} ${ARG_ADDITIONAL_HEADERS} ${ARG_UNPARSED_ARGUMENTS})
target_link_libraries("${name_static}" PRIVATE flang-rt-libcxx-headers flang-rt-libc-headers flang-rt-libc-static)
target_link_libraries("${name_static}" PRIVATE flang-rt-libcxx-headers flang-rt-libc-headers flang-rt-libc-static ${extra_deps})
endif ()
if (build_shared)
add_library("${name_shared}" SHARED ${extra_args} ${ARG_ADDITIONAL_HEADERS} ${ARG_UNPARSED_ARGUMENTS})
target_link_libraries("${name_shared}" PRIVATE flang-rt-libcxx-headers flang-rt-libc-headers flang-rt-libc-shared)
target_link_libraries("${name_shared}" PRIVATE flang-rt-libcxx-headers flang-rt-libc-headers flang-rt-libc-shared ${extra_deps})
if (Threads_FOUND)
target_link_libraries(${name_shared} PUBLIC Threads::Threads)
endif ()

View file

@ -109,7 +109,10 @@ public:
RT_API_ATTRS pointer_type operator->() const { return get(); }
private:
RT_API_ATTRS void delete_ptr(pointer_type p) { FreeMemory(p); }
RT_API_ATTRS void delete_ptr(pointer_type p) {
p->~A();
FreeMemory(p);
}
pointer_type ptr_{};
};

View file

@ -90,6 +90,9 @@ set(host_sources
time-intrinsic.cpp
unit-map.cpp
)
if (TARGET llvm-libc-common-utilities)
list(APPEND host_sources io-api-server.cpp)
endif()
# Sources that can be compiled directly for the GPU.
set(gpu_sources
@ -137,6 +140,7 @@ set(gpu_sources
reduce.cpp
reduction.cpp
temporary-stack.cpp
io-api-gpu.cpp
)
file(GLOB_RECURSE public_headers

View file

@ -0,0 +1,59 @@
//===-- lib/runtime/array.h -------------------------------------*- 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
//
//===----------------------------------------------------------------------===//
#ifndef FLANG_RT_RUNTIME_ARRAY_H_
#define FLANG_RT_RUNTIME_ARRAY_H_
#include "flang-rt/runtime/memory.h"
#include "flang-rt/runtime/terminator.h"
namespace Fortran::runtime {
// A simple dynamic array that only supports appending to avoid std::vector.
template <typename T> struct DynamicArray {
~DynamicArray() {
for (std::size_t i = 0; i < size_; ++i) {
data_[i].~T();
}
FreeMemory(data_);
}
void emplace_back(T &&value) {
if (size_ == capacity_) {
reserve(capacity_ ? capacity_ * 2 : 4);
}
new (data_ + size_) T(std::move(value));
++size_;
}
void reserve(std::size_t newCap) {
if (newCap <= capacity_) {
return;
}
T *new_data = static_cast<T *>(
AllocateMemoryOrCrash(terminator_, newCap * sizeof(T)));
for (std::size_t i = 0; i < size_; ++i) {
new (new_data + i) T(std::move(data_[i]));
data_[i].~T();
}
FreeMemory(data_);
data_ = new_data;
capacity_ = newCap;
}
T *begin() const { return data_; }
T *end() const { return data_ + size_; }
private:
T *data_ = nullptr;
std::size_t size_ = 0;
std::size_t capacity_ = 0;
Terminator terminator_{__FILE__, __LINE__};
};
} // namespace Fortran::runtime
#endif // FLANG_RT_RUNTIME_ARRAY_H_

View file

@ -0,0 +1,99 @@
//===-- lib/runtime/io-api-gpu.cpp ------------------------------*- 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
//
//===----------------------------------------------------------------------===//
// Implements the subset of the I/O statement API needed for basic
// list-directed output (PRINT *) of intrinsic types for the GPU.
//
// The RPC interface forwards each runtime call from the client to the server
// using a shared buffer. These calls are buffered on the server, so only the
// return value from 'BeginExternalListOutput' and 'EndIoStatement' are
// meaningful.
#include "io-api-gpu.h"
#include "flang/Runtime/io-api.h"
#include <shared/rpc.h>
#include <shared/rpc_dispatch.h>
namespace Fortran::runtime::io {
// A weak reference to the RPC client used to submit calls to the server.
[[gnu::weak, gnu::visibility("protected")]] rpc::Client client asm(
"__llvm_rpc_client");
RT_EXT_API_GROUP_BEGIN
Cookie IODEF(BeginExternalListOutput)(
ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
return rpc::dispatch<BeginExternalListOutput_Opcode>(client,
IONAME(BeginExternalListOutput), unitNumber, sourceFile, sourceLine);
}
enum Iostat IODEF(EndIoStatement)(Cookie cookie) {
return rpc::dispatch<EndIoStatement_Opcode>(
client, IONAME(EndIoStatement), cookie);
}
bool IODEF(OutputInteger8)(Cookie cookie, std::int8_t n) {
return rpc::dispatch<OutputInteger8_Opcode>(
client, IONAME(OutputInteger8), cookie, n);
}
bool IODEF(OutputInteger16)(Cookie cookie, std::int16_t n) {
return rpc::dispatch<OutputInteger16_Opcode>(
client, IONAME(OutputInteger16), cookie, n);
}
bool IODEF(OutputInteger32)(Cookie cookie, std::int32_t n) {
return rpc::dispatch<OutputInteger32_Opcode>(
client, IONAME(OutputInteger32), cookie, n);
}
bool IODEF(OutputInteger64)(Cookie cookie, std::int64_t n) {
return rpc::dispatch<OutputInteger64_Opcode>(
client, IONAME(OutputInteger64), cookie, n);
}
#ifdef __SIZEOF_INT128__
bool IODEF(OutputInteger128)(Cookie cookie, common::int128_t n) {
return rpc::dispatch<OutputInteger128_Opcode>(
client, IONAME(OutputInteger128), cookie, n);
}
#endif
bool IODEF(OutputReal32)(Cookie cookie, float x) {
return rpc::dispatch<OutputReal32_Opcode>(
client, IONAME(OutputReal32), cookie, x);
}
bool IODEF(OutputReal64)(Cookie cookie, double x) {
return rpc::dispatch<OutputReal64_Opcode>(
client, IONAME(OutputReal64), cookie, x);
}
bool IODEF(OutputComplex32)(Cookie cookie, float re, float im) {
return rpc::dispatch<OutputComplex32_Opcode>(
client, IONAME(OutputComplex32), cookie, re, im);
}
bool IODEF(OutputComplex64)(Cookie cookie, double re, double im) {
return rpc::dispatch<OutputComplex64_Opcode>(
client, IONAME(OutputComplex64), cookie, re, im);
}
bool IODEF(OutputAscii)(Cookie cookie, const char *x, std::size_t length) {
return rpc::dispatch<OutputAscii_Opcode>(
client, IONAME(OutputAscii), cookie, x, length);
}
bool IODEF(OutputLogical)(Cookie cookie, bool truth) {
return rpc::dispatch<OutputLogical_Opcode>(
client, IONAME(OutputLogical), cookie, truth);
}
RT_EXT_API_GROUP_END
} // namespace Fortran::runtime::io

View file

@ -0,0 +1,39 @@
//===-- lib/runtime/io-api-gpu.h --------------------------------*- 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
//
//===----------------------------------------------------------------------===//
#ifndef FLANG_RT_RUNTIME_IO_API_GPU_H_
#define FLANG_RT_RUNTIME_IO_API_GPU_H_
#include <cstdint>
namespace Fortran::runtime::io {
// We reserve the RPC opcodes with 'f' in the MSB for Fortran usage.
constexpr std::uint32_t MakeOpcode(std::uint32_t base) {
return ('f' << 24) | base;
}
// Opcodes shared between the client and server for each function we support.
enum RPCOpcodes : std::uint32_t {
BeginExternalListOutput_Opcode = MakeOpcode(0),
EndIoStatement_Opcode = MakeOpcode(1),
OutputInteger8_Opcode = MakeOpcode(2),
OutputInteger16_Opcode = MakeOpcode(3),
OutputInteger32_Opcode = MakeOpcode(4),
OutputInteger64_Opcode = MakeOpcode(5),
OutputInteger128_Opcode = MakeOpcode(6),
OutputReal32_Opcode = MakeOpcode(7),
OutputReal64_Opcode = MakeOpcode(8),
OutputComplex32_Opcode = MakeOpcode(9),
OutputComplex64_Opcode = MakeOpcode(10),
OutputAscii_Opcode = MakeOpcode(11),
OutputLogical_Opcode = MakeOpcode(12),
};
} // namespace Fortran::runtime::io
#endif // FLANG_RT_RUNTIME_IO_API_GPU_H_

View file

@ -0,0 +1,269 @@
//===-- lib/runtime/io-api-server.cpp ---------------------------*- 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
//
//===----------------------------------------------------------------------===//
// Implements the RPC server-side handlling of the I/O statement API needed for
// basic list-directed output (PRINT *) of intrinsic types for the GPU.
#include "array.h"
#include "io-api-gpu.h"
#include "flang-rt/runtime/memory.h"
#include "flang-rt/runtime/terminator.h"
#include "flang/Runtime/io-api.h"
#include <cstdlib>
#include <cstring>
#include <tuple>
#include <shared/rpc.h>
#include <shared/rpc_dispatch.h>
namespace Fortran::runtime::io {
namespace {
// Context used to chain the IO operations once run.
struct IOContext {
Cookie cookie = nullptr;
enum Iostat result = IostatOk;
};
// The base class to store deferred execution of a function. Uses function
// pointers for type erasure to avoid virtual dispatch.
struct DeferredFunctionBase {
using ExecuteFn = void (*)(void *, IOContext &);
using DestroyFn = void (*)(void *);
DeferredFunctionBase(void *impl, ExecuteFn exec, DestroyFn dtor)
: impl_(impl), execute_(exec), destroy_(dtor) {}
DeferredFunctionBase(const DeferredFunctionBase &) = delete;
DeferredFunctionBase &operator=(const DeferredFunctionBase &) = delete;
DeferredFunctionBase(DeferredFunctionBase &&other)
: impl_(other.impl_), execute_(other.execute_), destroy_(other.destroy_) {
other.impl_ = nullptr;
}
DeferredFunctionBase &operator=(DeferredFunctionBase &&other) {
if (this != &other) {
reset();
impl_ = other.impl_;
execute_ = other.execute_;
destroy_ = other.destroy_;
other.impl_ = nullptr;
}
return *this;
}
~DeferredFunctionBase() { reset(); }
void execute(IOContext &ctx) { execute_(impl_, ctx); }
static OwningPtr<char> TempString(const char *str) {
if (!str) {
return {};
}
const auto size = std::strlen(str) + 1;
OwningPtr<char> temp = SizedNew<char>{Terminator{__FILE__, __LINE__}}(size);
std::memcpy(temp.get(), str, size);
return OwningPtr<char>(temp.release());
}
private:
void reset() {
if (impl_) {
destroy_(impl_);
FreeMemory(impl_);
impl_ = nullptr;
}
}
void *impl_ = nullptr;
ExecuteFn execute_ = nullptr;
DestroyFn destroy_ = nullptr;
};
// Fortran does not support nested or recursive I/O, which is problematic for
// parallel execution on a GPU. To support this, we defer execution of runtime
// functions coming from the GPU's client until the end of that sequence is
// reached. This allows us to finish them in a single pass.
template <typename FnTy, typename... Args> struct DeferredFunction {
FnTy fn_;
std::tuple<std::decay_t<Args>...> args_;
DeferredFunction(FnTy &&fn, Args &&...args)
: fn_(std::forward<FnTy>(fn)), args_(std::forward<Args>(args)...) {}
// When executing the final command queue we need to replace the temporary
// values obtained from the GPU with the returned values from the actual
// runtime functions.
void execute(IOContext &ctx) {
auto caller = [&](auto &&...args) { return fn_(Rewrite(args, ctx)...); };
using RetTy = std::invoke_result_t<FnTy,
decltype(Rewrite(std::declval<Args &>(), ctx))...>;
if constexpr (std::is_same_v<RetTy, Cookie>) {
ctx.cookie = std::apply(caller, args_);
} else if constexpr (std::is_same_v<RetTy, Iostat>) {
ctx.result = std::apply(caller, args_);
} else {
std::apply(caller, args_);
}
}
private:
template <typename T> T &Rewrite(T &v, IOContext &) { return v; }
const char *Rewrite(OwningPtr<char> &p, IOContext &) { return p.get(); }
Cookie Rewrite(Cookie, IOContext &ctx) { return ctx.cookie; }
};
template <typename Fn, typename... Args>
DeferredFunctionBase MakeDeferred(Fn &&fn, Args &&...args) {
Terminator terminator{__FILE__, __LINE__};
using Ty = DeferredFunction<Fn, Args...>;
auto ptr = SizedNew<Ty>{terminator}(
sizeof(Ty), std::forward<Fn>(fn), std::forward<Args>(args)...);
void *raw = ptr.release();
return DeferredFunctionBase(
raw,
[](void *self, IOContext &ctx) { static_cast<Ty *>(self)->execute(ctx); },
[](void *self) { static_cast<Ty *>(self)->~Ty(); });
}
// The context associated with the queue of deferred functions. This serves as
// our cookie object while executing this on the GPU.
struct DeferredContext {
IOContext ioCtx;
DynamicArray<DeferredFunctionBase> commands;
};
template <typename FnTy, typename... Args>
bool EnqueueDeferred(FnTy &&fn, Cookie cookie, Args &&...args) {
DeferredContext *ctx = reinterpret_cast<DeferredContext *>(cookie);
ctx->commands.emplace_back(
MakeDeferred(fn, cookie, std::forward<Args>(args)...));
return true;
}
template <std::uint32_t NumLanes>
rpc::Status HandleOpcodesImpl(rpc::Server::Port &port) {
switch (port.get_opcode()) {
case BeginExternalListOutput_Opcode:
rpc::invoke<NumLanes>(port,
[](ExternalUnit unitNumber, const char *sourceFile,
int sourceLine) -> Cookie {
DeferredContext *ctx = new (AllocateMemoryOrCrash(
Terminator{__FILE__, __LINE__}, sizeof(DeferredContext)))
DeferredContext;
ctx->commands.emplace_back(
MakeDeferred(IONAME(BeginExternalListOutput), unitNumber,
DeferredFunctionBase::TempString(sourceFile), sourceLine));
return reinterpret_cast<Cookie>(ctx);
});
break;
case EndIoStatement_Opcode:
rpc::invoke<NumLanes>(port, [](Cookie cookie) -> Iostat {
DeferredContext *ctx = reinterpret_cast<DeferredContext *>(cookie);
ctx->commands.emplace_back(MakeDeferred(IONAME(EndIoStatement), cookie));
for (auto &fn : ctx->commands) {
fn.execute(ctx->ioCtx);
}
Iostat result = ctx->ioCtx.result;
ctx->~DeferredContext();
FreeMemory(ctx);
return result;
});
break;
case OutputAscii_Opcode:
rpc::invoke<NumLanes>(
port, [](Cookie cookie, const char *x, std::size_t length) -> bool {
return EnqueueDeferred(IONAME(OutputAscii), cookie,
DeferredFunctionBase::TempString(x), length);
});
break;
case OutputInteger8_Opcode:
rpc::invoke<NumLanes>(port, [](Cookie cookie, std::int8_t n) -> bool {
return EnqueueDeferred(IONAME(OutputInteger8), cookie, n);
});
break;
case OutputInteger16_Opcode:
rpc::invoke<NumLanes>(port, [](Cookie cookie, std::int16_t n) -> bool {
return EnqueueDeferred(IONAME(OutputInteger16), cookie, n);
});
break;
case OutputInteger32_Opcode:
rpc::invoke<NumLanes>(port, [](Cookie cookie, std::int32_t n) -> bool {
return EnqueueDeferred(IONAME(OutputInteger32), cookie, n);
});
break;
case OutputInteger64_Opcode:
rpc::invoke<NumLanes>(port, [](Cookie cookie, std::int64_t n) -> bool {
return EnqueueDeferred(IONAME(OutputInteger64), cookie, n);
});
break;
#ifdef __SIZEOF_INT128__
case OutputInteger128_Opcode:
rpc::invoke<NumLanes>(port, [](Cookie cookie, common::int128_t n) -> bool {
return EnqueueDeferred(IONAME(OutputInteger128), cookie, n);
});
break;
#endif
case OutputReal32_Opcode:
rpc::invoke<NumLanes>(port, [](Cookie cookie, float x) -> bool {
return EnqueueDeferred(IONAME(OutputReal32), cookie, x);
});
break;
case OutputReal64_Opcode:
rpc::invoke<NumLanes>(port, [](Cookie cookie, double x) -> bool {
return EnqueueDeferred(IONAME(OutputReal64), cookie, x);
});
break;
case OutputComplex32_Opcode:
rpc::invoke<NumLanes>(port, [](Cookie cookie, float re, float im) -> bool {
return EnqueueDeferred(IONAME(OutputComplex32), cookie, re, im);
});
break;
case OutputComplex64_Opcode:
rpc::invoke<NumLanes>(
port, [](Cookie cookie, double re, double im) -> bool {
return EnqueueDeferred(IONAME(OutputComplex64), cookie, re, im);
});
break;
case OutputLogical_Opcode:
rpc::invoke<NumLanes>(port, [](Cookie cookie, bool truth) -> bool {
return EnqueueDeferred(IONAME(OutputLogical), cookie, truth);
});
break;
default:
return rpc::RPC_UNHANDLED_OPCODE;
}
return rpc::RPC_SUCCESS;
}
} // namespace
RT_EXT_API_GROUP_BEGIN
std::uint32_t IODEF(HandleRPCOpcodes)(void *raw, std::uint32_t numLanes) {
rpc::Server::Port &port = *reinterpret_cast<rpc::Server::Port *>(raw);
switch (numLanes) {
case 1:
return HandleOpcodesImpl<1>(port);
case 32:
return HandleOpcodesImpl<32>(port);
case 64:
return HandleOpcodesImpl<64>(port);
default:
return rpc::RPC_ERROR;
}
}
RT_EXT_API_GROUP_END
} // namespace Fortran::runtime::io

View file

@ -364,6 +364,8 @@ bool IODECL(InquireInteger64)(
// rather than by terminating the image.
enum Iostat IODECL(EndIoStatement)(Cookie);
// Used for I/O from the offloading device.
std::uint32_t IODECL(HandleRPCOpcodes)(void *raw, std::uint32_t numLanes);
} // extern "C"
} // namespace Fortran::runtime::io

View file

@ -35,6 +35,11 @@ endif()
include(FindLibcCommonUtils)
target_link_libraries(PluginCommon PRIVATE llvm-libc-common-utilities)
if (TARGET flang_rt.runtime.static)
target_link_libraries(PluginCommon PRIVATE flang_rt.runtime.static)
target_compile_definitions(PluginCommon PRIVATE OFFLOAD_HAS_FLANG_RT)
endif()
# Define the TARGET_NAME and DEBUG_PREFIX.
target_compile_definitions(PluginCommon PRIVATE
TARGET_NAME=PluginInterface

View file

@ -17,6 +17,10 @@
#include "shared/rpc_opcodes.h"
#include "shared/rpc_server.h"
#ifdef OFFLOAD_HAS_FLANG_RT
#include "flang/Runtime/io-api.h"
#endif
using namespace llvm;
using namespace omp;
using namespace target;
@ -111,6 +115,12 @@ runServer(plugin::GenericDeviceTy &Device, void *Buffer,
if (Status == rpc::RPC_UNHANDLED_OPCODE)
Status = LIBC_NAMESPACE::shared::handle_libc_opcodes(*Port, NumLanes);
#ifdef OFFLOAD_HAS_FLANG_RT
if (Status == rpc::RPC_UNHANDLED_OPCODE)
Status = static_cast<rpc::Status>(
Fortran::runtime::io::IONAME(HandleRPCOpcodes)(&*Port, NumLanes));
#endif
return Status;
}

View file

@ -0,0 +1,27 @@
! REQUIRES: flang, libc
! RUN: %libomptarget-compile-fortran-run-and-check-generic
program io_test
implicit none
integer :: i
real :: r
complex :: c
logical :: l
i = 42
r = 3.14
c = (1.0, -1.0)
l = .true.
! CHECK: Text 42 3.14 (1.,-1.) T
! CHECK: Text 42 3.14 (1.,-1.) T
! CHECK: Text 42 3.14 (1.,-1.) T
! CHECK: Text 42 3.14 (1.,-1.) T
!$omp target teams num_teams(4)
!$omp parallel num_threads(1)
print *, "Text", " ", i, " ", r, " ", c, " ", l
!$omp end parallel
!$omp end target teams
end program io_test

View file

@ -33,10 +33,11 @@ list(INSERT CMAKE_MODULE_PATH 0
"${CMAKE_CURRENT_SOURCE_DIR}/../llvm/cmake/modules"
)
# We order libraries to mirror roughly how they are layered, except that compiler-rt can depend
# on libc++, so we put it after.
# We order libraries to mirror roughly how they are layered, except that
# compiler-rt can depend on libc++, so we put it after. Offload can depend on
# flang-rt so we order it before.
set(LLVM_DEFAULT_RUNTIMES "libc;libunwind;libcxxabi;libcxx;compiler-rt;libclc;openmp;offload")
set(LLVM_SUPPORTED_RUNTIMES "${LLVM_DEFAULT_RUNTIMES};llvm-libgcc;flang-rt;libsycl;orc-rt")
set(LLVM_SUPPORTED_RUNTIMES "libc;libunwind;libcxxabi;libcxx;compiler-rt;libclc;openmp;flang-rt;offload;llvm-libgcc;libsycl;orc-rt")
set(LLVM_ENABLE_RUNTIMES "" CACHE STRING
"Semicolon-separated list of runtimes to build, or \"all\" (${LLVM_DEFAULT_RUNTIMES}). Supported runtimes are ${LLVM_SUPPORTED_RUNTIMES}.")
if(LLVM_ENABLE_RUNTIMES STREQUAL "all" )