88
99#include " DeviceCompilation.h"
1010
11+ #include < clang/Basic/DiagnosticDriver.h>
1112#include < clang/Basic/Version.h>
1213#include < clang/CodeGen/CodeGenAction.h>
1314#include < clang/Driver/Compilation.h>
15+ #include < clang/Driver/Options.h>
1416#include < clang/Frontend/CompilerInstance.h>
17+ #include < clang/Frontend/TextDiagnosticBuffer.h>
1518#include < clang/Tooling/CompilationDatabase.h>
1619#include < clang/Tooling/Tooling.h>
1720
2326using namespace clang ;
2427using namespace clang ::tooling;
2528using namespace clang ::driver;
29+ using namespace clang ::driver::options;
2630using namespace llvm ;
31+ using namespace llvm ::opt;
2732
2833#ifdef _GNU_SOURCE
2934#include < dlfcn.h>
@@ -106,16 +111,33 @@ struct GetLLVMModuleAction : public ToolAction {
106111Expected<std::unique_ptr<llvm::Module>>
107112jit_compiler::compileDeviceCode (InMemoryFile SourceFile,
108113 View<InMemoryFile> IncludeFiles,
109- View< const char *> UserArgs ) {
114+ const InputArgList &UserArgList ) {
110115 const std::string &DPCPPRoot = getDPCPPRoot ();
111116 if (DPCPPRoot == InvalidDPCPPRoot) {
112117 return createStringError (" Could not locate DPCPP root directory" );
113118 }
114119
115- SmallVector<std::string> CommandLine = {" -fsycl-device-only" };
116- CommandLine.append (UserArgs.begin (), UserArgs.end ());
117- FixedCompilationDatabase DB{" ." , CommandLine};
120+ DerivedArgList DAL{UserArgList};
121+ const auto &OptTable = getDriverOptTable ();
122+ DAL.AddFlagArg (nullptr , OptTable.getOption (OPT_fsycl_device_only));
123+ DAL.AddJoinedArg (
124+ nullptr , OptTable.getOption (OPT_resource_dir_EQ),
125+ (DPCPPRoot + " /lib/clang/" + Twine (CLANG_VERSION_MAJOR)).str ());
126+ for (auto *Arg : UserArgList) {
127+ DAL.append (Arg);
128+ }
129+ // Remove args that will trigger an unused command line argument warning for
130+ // the FrontendAction invocation, but are handled later (e.g. during device
131+ // linking).
132+ DAL.eraseArg (OPT_fsycl_device_lib_EQ);
133+ DAL.eraseArg (OPT_fno_sycl_device_lib_EQ);
134+
135+ SmallVector<std::string> CommandLine;
136+ for (auto *Arg : DAL) {
137+ CommandLine.emplace_back (Arg->getAsString (DAL));
138+ }
118139
140+ FixedCompilationDatabase DB{" ." , CommandLine};
119141 ClangTool Tool{DB, {SourceFile.Path }};
120142
121143 // Set up in-memory filesystem.
@@ -127,17 +149,14 @@ jit_compiler::compileDeviceCode(InMemoryFile SourceFile,
127149 // Reset argument adjusters to drop the `-fsyntax-only` flag which is added by
128150 // default by this API.
129151 Tool.clearArgumentsAdjusters ();
130- // Then, modify argv[0] and set the resource directory so that the driver
131- // picks up the correct SYCL environment .
152+ // Then, modify argv[0] so that the driver picks up the correct SYCL
153+ // environment. We've already set the resource directory above .
132154 Tool.appendArgumentsAdjuster (
133155 [&DPCPPRoot](const CommandLineArguments &Args,
134156 StringRef Filename) -> CommandLineArguments {
135157 (void )Filename;
136158 CommandLineArguments NewArgs = Args;
137159 NewArgs[0 ] = (Twine (DPCPPRoot) + " /bin/clang++" ).str ();
138- NewArgs.push_back ((Twine (" -resource-dir=" ) + DPCPPRoot + " /lib/clang/" +
139- Twine (CLANG_VERSION_MAJOR))
140- .str ());
141160 return NewArgs;
142161 });
143162
@@ -150,87 +169,197 @@ jit_compiler::compileDeviceCode(InMemoryFile SourceFile,
150169 return createStringError (" Unable to obtain LLVM module" );
151170}
152171
153- Error jit_compiler::linkDefaultDeviceLibraries (llvm::Module &Module,
154- View<const char *> UserArgs) {
155- // This function mimics the device library selection process
156- // `clang::driver::tools::SYCL::getDeviceLibraries`, assuming a SPIR-V target
157- // (no AoT, no third-party GPUs, no native CPU).
172+ // This function is a simplified copy of the device library selection process in
173+ // `clang::driver::tools::SYCL::getDeviceLibraries`, assuming a SPIR-V target
174+ // (no AoT, no third-party GPUs, no native CPU). Keep in sync!
175+ static SmallVector<std::string, 8 >
176+ getDeviceLibraries (const ArgList &Args, DiagnosticsEngine &Diags) {
177+ struct DeviceLibOptInfo {
178+ StringRef DeviceLibName;
179+ StringRef DeviceLibOption;
180+ };
181+
182+ // Currently, all SYCL device libraries will be linked by default.
183+ llvm::StringMap<bool > DeviceLibLinkInfo = {
184+ {" libc" , true }, {" libm-fp32" , true }, {" libm-fp64" , true },
185+ {" libimf-fp32" , true }, {" libimf-fp64" , true }, {" libimf-bf16" , true },
186+ {" libm-bfloat16" , true }, {" internal" , true }};
187+
188+ // If -fno-sycl-device-lib is specified, its values will be used to exclude
189+ // linkage of libraries specified by DeviceLibLinkInfo. Linkage of "internal"
190+ // libraries cannot be affected via -fno-sycl-device-lib.
191+ bool ExcludeDeviceLibs = false ;
192+
193+ if (Arg *A = Args.getLastArg (OPT_fsycl_device_lib_EQ,
194+ OPT_fno_sycl_device_lib_EQ)) {
195+ if (A->getValues ().size () == 0 ) {
196+ Diags.Report (diag::warn_drv_empty_joined_argument)
197+ << A->getAsString (Args);
198+ } else {
199+ if (A->getOption ().matches (OPT_fno_sycl_device_lib_EQ)) {
200+ ExcludeDeviceLibs = true ;
201+ }
202+
203+ for (StringRef Val : A->getValues ()) {
204+ if (Val == " all" ) {
205+ for (const auto &K : DeviceLibLinkInfo.keys ()) {
206+ DeviceLibLinkInfo[K] = (K == " internal" ) || !ExcludeDeviceLibs;
207+ }
208+ break ;
209+ }
210+ auto LinkInfoIter = DeviceLibLinkInfo.find (Val);
211+ if (LinkInfoIter == DeviceLibLinkInfo.end () || Val == " internal" ) {
212+ Diags.Report (diag::err_drv_unsupported_option_argument)
213+ << A->getSpelling () << Val;
214+ return {};
215+ }
216+ DeviceLibLinkInfo[Val] = !ExcludeDeviceLibs;
217+ }
218+ }
219+ }
220+
221+ using SYCLDeviceLibsList = SmallVector<DeviceLibOptInfo, 5 >;
222+
223+ const SYCLDeviceLibsList SYCLDeviceWrapperLibs = {
224+ {" libsycl-crt" , " libc" },
225+ {" libsycl-complex" , " libm-fp32" },
226+ {" libsycl-complex-fp64" , " libm-fp64" },
227+ {" libsycl-cmath" , " libm-fp32" },
228+ {" libsycl-cmath-fp64" , " libm-fp64" },
229+ {" libsycl-imf" , " libimf-fp32" },
230+ {" libsycl-imf-fp64" , " libimf-fp64" },
231+ {" libsycl-imf-bf16" , " libimf-bf16" }};
232+ // ITT annotation libraries are linked in separately whenever the device
233+ // code instrumentation is enabled.
234+ const SYCLDeviceLibsList SYCLDeviceAnnotationLibs = {
235+ {" libsycl-itt-user-wrappers" , " internal" },
236+ {" libsycl-itt-compiler-wrappers" , " internal" },
237+ {" libsycl-itt-stubs" , " internal" }};
238+ const SYCLDeviceLibsList SYCLDeviceSanitizerLibs = {
239+ {" libsycl-sanitizer" , " internal" }};
240+
241+ SmallVector<std::string, 8 > LibraryList;
242+ StringRef LibSuffix = " .bc" ;
243+ auto AddLibraries = [&](const SYCLDeviceLibsList &LibsList) {
244+ for (const DeviceLibOptInfo &Lib : LibsList) {
245+ if (!DeviceLibLinkInfo[Lib.DeviceLibOption ]) {
246+ continue ;
247+ }
248+ SmallString<128 > LibName (Lib.DeviceLibName );
249+ llvm::sys::path::replace_extension (LibName, LibSuffix);
250+ LibraryList.push_back (Args.MakeArgString (LibName));
251+ }
252+ };
253+
254+ AddLibraries (SYCLDeviceWrapperLibs);
255+
256+ if (Args.hasFlag (OPT_fsycl_instrument_device_code,
257+ OPT_fno_sycl_instrument_device_code, false )) {
258+ AddLibraries (SYCLDeviceAnnotationLibs);
259+ }
158260
159- bool DeviceInstrumentationEnabled = false ;
160- for (StringRef UA : UserArgs) {
161- // Check instrumentation-related flags (last occurence determines outcome).
162- if (UA == " -fsycl-instrument-device-code" ) {
163- DeviceInstrumentationEnabled = true ;
164- continue ;
261+ if (Arg *A = Args.getLastArg (OPT_fsanitize_EQ, OPT_fno_sanitize_EQ)) {
262+ if (A->getOption ().matches (OPT_fsanitize_EQ) &&
263+ A->getValues ().size () == 1 ) {
264+ std::string SanitizeVal = A->getValue ();
265+ if (SanitizeVal == " address" ) {
266+ AddLibraries (SYCLDeviceSanitizerLibs);
267+ }
165268 }
166- if (UA == " -fno-sycl-instrument-device-code" ) {
167- DeviceInstrumentationEnabled = false ;
168- continue ;
269+ } else {
270+ // User can pass -fsanitize=address to device compiler via
271+ // -Xsycl-target-frontend, sanitize device library must be
272+ // linked with user's device image if so.
273+ bool IsDeviceAsanEnabled = false ;
274+ auto SyclFEArg = Args.getAllArgValues (OPT_Xsycl_frontend);
275+ IsDeviceAsanEnabled = (std::count (SyclFEArg.begin (), SyclFEArg.end (),
276+ " -fsanitize=address" ) > 0 );
277+ if (!IsDeviceAsanEnabled) {
278+ auto SyclFEArgEq = Args.getAllArgValues (OPT_Xsycl_frontend_EQ);
279+ IsDeviceAsanEnabled = (std::count (SyclFEArgEq.begin (), SyclFEArgEq.end (),
280+ " -fsanitize=address" ) > 0 );
169281 }
170282
171- // Issue warning for `-fsycl-device-lib` or `-fno-sycl-device-lib`.
172- // TODO: Is it worth supporting these flags? We're using `LinkOnlyNeeded`
173- // mode anyways!
174- // TODO: If we keep the warning, it must go into the build log, not onto the
175- // console.
176- // TODO: The FrontendAction emits a warning that these flags are unused. We
177- // should probably silence that by removing the argument occurence for
178- // the compilation step.
179- if (UA.contains (" sycl-device-lib" )) {
180- errs () << " warning: device library selection with '" << UA
181- << " ' is ignored\n " ;
283+ // User can also enable asan for SYCL device via -Xarch_device option.
284+ if (!IsDeviceAsanEnabled) {
285+ auto DeviceArchVals = Args.getAllArgValues (OPT_Xarch_device);
286+ for (auto DArchVal : DeviceArchVals) {
287+ if (DArchVal.find (" -fsanitize=address" ) != std::string::npos) {
288+ IsDeviceAsanEnabled = true ;
289+ break ;
290+ }
291+ }
182292 }
183293
184- // TODO: Presence of `-fsanitize=address` would require linking
185- // `libsycl-sanitizer`, but currently compilation fails earlier.
186- assert (!UA. contains ( " -fsanitize=address " ) && " Device ASAN unsupported " );
294+ if (IsDeviceAsanEnabled) {
295+ AddLibraries (SYCLDeviceSanitizerLibs);
296+ }
187297 }
188298
299+ return LibraryList;
300+ }
301+
302+ Error jit_compiler::linkDeviceLibraries (llvm::Module &Module,
303+ const InputArgList &UserArgList) {
304+ // This function mimics the device library selection process
305+ // `clang::driver::tools::SYCL::getDeviceLibraries`, assuming a SPIR-V target
306+ // (no AoT, no third-party GPUs, no native CPU).
307+
189308 const std::string &DPCPPRoot = getDPCPPRoot ();
190309 if (DPCPPRoot == InvalidDPCPPRoot) {
191310 return createStringError (" Could not locate DPCPP root directory" );
192311 }
193312
194- constexpr std::array<llvm::StringLiteral, 8 > SYCLDeviceWrapperLibs = {
195- " libsycl-crt" , " libsycl-complex" , " libsycl-complex-fp64" ,
196- " libsycl-cmath" , " libsycl-cmath-fp64" , " libsycl-imf" ,
197- " libsycl-imf-fp64" , " libsycl-imf-bf16" };
198-
199- constexpr std::array<llvm::StringLiteral, 3 > SYCLDeviceAnnotationLibs = {
200- " libsycl-itt-user-wrappers" , " libsycl-itt-compiler-wrappers" ,
201- " libsycl-itt-stubs" };
313+ // TODO: Seems a bit excessive to set up this machinery for one warning and
314+ // one error. Rethink when implementing the build log/error reporting as
315+ // mandated by the extension.
316+ IntrusiveRefCntPtr<DiagnosticIDs> DiagID{new DiagnosticIDs};
317+ IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts{new DiagnosticOptions};
318+ TextDiagnosticBuffer *DiagBuffer = new TextDiagnosticBuffer;
319+ DiagnosticsEngine Diags (DiagID, DiagOpts, DiagBuffer);
320+
321+ auto LibNames = getDeviceLibraries (UserArgList, Diags);
322+ if (LibNames.empty ()) {
323+ assert (std::distance (DiagBuffer->err_begin (), DiagBuffer->err_end ()) == 1 );
324+ return createStringError (" Could not determine list of device libraries: %s" ,
325+ DiagBuffer->err_begin ()->second .c_str ());
326+ }
327+ // TODO: Add warnings to build log.
202328
203329 LLVMContext &Context = Module.getContext ();
204- auto Link = [&](ArrayRef<llvm::StringLiteral> LibNames) -> Error {
205- for (const auto &LibName : LibNames) {
206- std::string LibPath = (DPCPPRoot + " /lib/" + LibName + " .bc" ).str ();
207-
208- SMDiagnostic Diag;
209- std::unique_ptr<llvm::Module> Lib = parseIRFile (LibPath, Diag, Context);
210- if (!Lib) {
211- std::string DiagMsg;
212- raw_string_ostream SOS (DiagMsg);
213- Diag.print (/* ProgName=*/ nullptr , SOS);
214- return createStringError (DiagMsg);
215- }
216-
217- if (Linker::linkModules (Module, std::move (Lib), Linker::LinkOnlyNeeded)) {
218- // TODO: `linkModules` always prints errors to the console.
219- return createStringError (" Unable to link device library: %s" ,
220- LibPath.c_str ());
221- }
330+ for (const std::string &LibName : LibNames) {
331+ std::string LibPath = DPCPPRoot + " /lib/" + LibName;
332+
333+ SMDiagnostic Diag;
334+ std::unique_ptr<llvm::Module> Lib = parseIRFile (LibPath, Diag, Context);
335+ if (!Lib) {
336+ std::string DiagMsg;
337+ raw_string_ostream SOS (DiagMsg);
338+ Diag.print (/* ProgName=*/ nullptr , SOS);
339+ return createStringError (DiagMsg);
222340 }
223341
224- return Error::success ();
225- };
226-
227- if (auto Error = Link (SYCLDeviceWrapperLibs)) {
228- return Error;
229- }
230- if (DeviceInstrumentationEnabled) {
231- if (auto Error = Link (SYCLDeviceAnnotationLibs)) {
232- return Error;
342+ if (Linker::linkModules (Module, std::move (Lib), Linker::LinkOnlyNeeded)) {
343+ // TODO: Obtain detailed error message from the context's diagnostics
344+ // handler.
345+ return createStringError (" Unable to link device library: %s" ,
346+ LibPath.c_str ());
233347 }
234348 }
349+
235350 return Error::success ();
236351}
352+
353+ Expected<InputArgList>
354+ jit_compiler::parseUserArgs (View<const char *> UserArgs) {
355+ unsigned MissingArgIndex, MissingArgCount;
356+ auto UserArgsRef = UserArgs.to <ArrayRef>();
357+ auto AL = getDriverOptTable ().ParseArgs (UserArgsRef, MissingArgIndex,
358+ MissingArgCount);
359+ if (MissingArgCount) {
360+ return createStringError (
361+ " User option '%s' at index %d is missing an argument" ,
362+ UserArgsRef[MissingArgIndex], MissingArgIndex);
363+ }
364+ return AL;
365+ }
0 commit comments