1 //============================================================================
2 // Copyright (c) Kitware, Inc.
3 // All rights reserved.
4 // See LICENSE.txt for details.
5 //
6 // This software is distributed WITHOUT ANY WARRANTY; without even
7 // the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8 // PURPOSE. See the above copyright notice for more information.
9 //============================================================================
10
11 #include <vtkm/cont/Initialize.h>
12
13 #include <vtkm/cont/Logging.h>
14 #include <vtkm/cont/RuntimeDeviceTracker.h>
15 #include <vtkm/cont/internal/OptionParser.h>
16
17 #if defined(VTKM_ENABLE_KOKKOS)
18 #include <vtkm/cont/kokkos/internal/Initialize.h>
19 #endif
20
21 #include <memory>
22 #include <sstream>
23
24 namespace opt = vtkm::cont::internal::option;
25
26 namespace
27 {
28
29 enum OptionIndex
30 {
31 UNKNOWN,
32 DEVICE,
33 LOGLEVEL, // not parsed by this parser, but by loguru
34 HELP
35 };
36
37 struct VtkmArg : public opt::Arg
38 {
IsDevice__anonac7fdb5d0111::VtkmArg39 static opt::ArgStatus IsDevice(const opt::Option& option, bool msg)
40 {
41 // Device must be specified if option is present:
42 if (option.arg == nullptr)
43 {
44 if (msg)
45 {
46 VTKM_LOG_ALWAYS_S(vtkm::cont::LogLevel::Error,
47 "Missing device after option '"
48 << std::string(option.name, static_cast<size_t>(option.namelen))
49 << "'.\nValid devices are: " << VtkmArg::GetValidDeviceNames() << "\n");
50 }
51 return opt::ARG_ILLEGAL;
52 }
53
54 auto id = vtkm::cont::make_DeviceAdapterId(option.arg);
55
56 if (!VtkmArg::DeviceIsAvailable(id))
57 {
58 VTKM_LOG_ALWAYS_S(vtkm::cont::LogLevel::Error,
59 "Unavailable device specificed after option '"
60 << std::string(option.name, static_cast<size_t>(option.namelen)) << "': '"
61 << option.arg
62 << "'.\nValid devices are: " << VtkmArg::GetValidDeviceNames() << "\n");
63 return opt::ARG_ILLEGAL;
64 }
65
66 return opt::ARG_OK;
67 }
68
DeviceIsAvailable__anonac7fdb5d0111::VtkmArg69 static bool DeviceIsAvailable(vtkm::cont::DeviceAdapterId id)
70 {
71 if (id == vtkm::cont::DeviceAdapterTagAny{})
72 {
73 return true;
74 }
75
76 if (id.GetValue() <= 0 || id.GetValue() >= VTKM_MAX_DEVICE_ADAPTER_ID ||
77 id == vtkm::cont::DeviceAdapterTagUndefined{})
78 {
79 return false;
80 }
81
82 auto& tracker = vtkm::cont::GetRuntimeDeviceTracker();
83 bool result = false;
84 try
85 {
86 result = tracker.CanRunOn(id);
87 }
88 catch (...)
89 {
90 result = false;
91 }
92 return result;
93 }
94
GetValidDeviceNames__anonac7fdb5d0111::VtkmArg95 static std::string GetValidDeviceNames()
96 {
97 std::ostringstream names;
98 names << "\"Any\" ";
99
100 for (vtkm::Int8 i = 0; i < VTKM_MAX_DEVICE_ADAPTER_ID; ++i)
101 {
102 auto id = vtkm::cont::make_DeviceAdapterId(i);
103 if (VtkmArg::DeviceIsAvailable(id))
104 {
105 names << "\"" << id.GetName() << "\" ";
106 }
107 }
108 return names.str();
109 }
110
Required__anonac7fdb5d0111::VtkmArg111 static opt::ArgStatus Required(const opt::Option& option, bool msg)
112 {
113 if (option.arg == nullptr)
114 {
115 if (msg)
116 {
117 VTKM_LOG_ALWAYS_S(vtkm::cont::LogLevel::Error,
118 "Missing argument after option '"
119 << std::string(option.name, static_cast<size_t>(option.namelen))
120 << "'.\n");
121 }
122 return opt::ARG_ILLEGAL;
123 }
124 else
125 {
126 return opt::ARG_OK;
127 }
128 }
129
130 // Method used for guessing whether an option that do not support (perhaps that calling
131 // program knows about it) has an option attached to it (which should also be ignored).
UnknownOption__anonac7fdb5d0111::VtkmArg132 static opt::ArgStatus UnknownOption(const opt::Option& option, bool msg)
133 {
134 // If we don't have an arg, obviously we don't have an arg.
135 if (option.arg == nullptr)
136 {
137 return opt::ARG_NONE;
138 }
139
140 // The opt::Arg::Optional method will return that the ARG is OK if and only if
141 // the argument is attached to the option (e.g. --foo=bar). If that is the case,
142 // then we definitely want to report that the argument is OK.
143 if (opt::Arg::Optional(option, msg) == opt::ARG_OK)
144 {
145 return opt::ARG_OK;
146 }
147
148 // Now things get tricky. Maybe the next argument is an option or maybe it is an
149 // argument for this option. We will guess that if the next argument does not
150 // look like an option, we will treat it as such.
151 if (option.arg[0] == '-')
152 {
153 return opt::ARG_NONE;
154 }
155 else
156 {
157 return opt::ARG_OK;
158 }
159 }
160 };
161
162 } // end anon namespace
163
164 namespace vtkm
165 {
166 namespace cont
167 {
168
169 VTKM_CONT
Initialize(int & argc,char * argv[],InitializeOptions opts)170 InitializeResult Initialize(int& argc, char* argv[], InitializeOptions opts)
171 {
172 InitializeResult config;
173
174 // initialize logging first -- it'll pop off the options it consumes:
175 if (argc == 0 || argv == nullptr)
176 {
177 vtkm::cont::InitLogging();
178 }
179 else
180 {
181 vtkm::cont::InitLogging(argc, argv);
182 }
183
184 #ifdef VTKM_ENABLE_KOKKOS
185 vtkm::cont::kokkos::internal::Initialize(argc, argv);
186 #endif
187
188 { // Parse VTKm options
189 std::vector<opt::Descriptor> usage;
190 if ((opts & InitializeOptions::AddHelp) != InitializeOptions::None)
191 {
192 usage.push_back({ UNKNOWN, 0, "", "", VtkmArg::UnknownOption, "Usage information:\n" });
193 }
194 usage.push_back(
195 { DEVICE,
196 0,
197 "d",
198 "device",
199 VtkmArg::IsDevice,
200 " --device, -d <dev> \tForce device to dev. Omit device to list available devices." });
201 usage.push_back(
202 { LOGLEVEL,
203 0,
204 "v",
205 "",
206 VtkmArg::Required,
207 " -v <#|INFO|WARNING|ERROR|FATAL|OFF> \tSpecify a log level (when logging is enabled)." });
208 if ((opts & InitializeOptions::AddHelp) != InitializeOptions::None)
209 {
210 usage.push_back(
211 { HELP, 0, "h", "help", opt::Arg::None, " --help, -h \tPrint usage information." });
212 }
213 // Required to collect unknown arguments when help is off.
214 usage.push_back({ UNKNOWN, 0, "", "", VtkmArg::UnknownOption, "" });
215 usage.push_back({ 0, 0, 0, 0, 0, 0 });
216
217 {
218 std::stringstream streamBuffer;
219 opt::printUsage(streamBuffer, usage.data());
220 config.Usage = streamBuffer.str();
221 // Remove trailing newline as one more than we want is added.
222 config.Usage = config.Usage.substr(0, config.Usage.length() - 1);
223 }
224
225 // Remove argv[0] (executable name) if present:
226 int vtkmArgc = argc > 0 ? argc - 1 : 0;
227 char** vtkmArgv = vtkmArgc > 0 ? argv + 1 : argv;
228
229 opt::Stats stats(usage.data(), vtkmArgc, vtkmArgv);
230 std::unique_ptr<opt::Option[]> options{ new opt::Option[stats.options_max] };
231 std::unique_ptr<opt::Option[]> buffer{ new opt::Option[stats.buffer_max] };
232 opt::Parser parse(usage.data(), vtkmArgc, vtkmArgv, options.get(), buffer.get());
233
234 if (parse.error())
235 {
236 std::cerr << config.Usage;
237 exit(1);
238 }
239
240 if (options[HELP])
241 {
242 std::cerr << config.Usage;
243 exit(0);
244 }
245
246 if (options[DEVICE])
247 {
248 auto id = vtkm::cont::make_DeviceAdapterId(options[DEVICE].arg);
249 if (id != vtkm::cont::DeviceAdapterTagAny{})
250 {
251 vtkm::cont::GetRuntimeDeviceTracker().ForceDevice(id);
252 }
253 else
254 {
255 vtkm::cont::GetRuntimeDeviceTracker().Reset();
256 }
257 config.Device = id;
258 }
259 else if ((opts & InitializeOptions::DefaultAnyDevice) != InitializeOptions::None)
260 {
261 vtkm::cont::GetRuntimeDeviceTracker().Reset();
262 config.Device = vtkm::cont::DeviceAdapterTagAny{};
263 }
264 else if ((opts & InitializeOptions::RequireDevice) != InitializeOptions::None)
265 {
266 auto devices = VtkmArg::GetValidDeviceNames();
267 VTKM_LOG_S(vtkm::cont::LogLevel::Error, "Device not given on command line.");
268 std::cerr << "Target device must be specified via -d or --device.\n"
269 "Valid devices: "
270 << devices << std::endl;
271 if ((opts & InitializeOptions::AddHelp) != InitializeOptions::None)
272 {
273 std::cerr << config.Usage;
274 }
275 exit(1);
276 }
277
278 for (const opt::Option* opt = options[UNKNOWN]; opt != nullptr; opt = opt->next())
279 {
280 if ((opts & InitializeOptions::ErrorOnBadOption) != InitializeOptions::None)
281 {
282 std::cerr << "Unknown option: " << opt->name << std::endl;
283 if ((opts & InitializeOptions::AddHelp) != InitializeOptions::None)
284 {
285 std::cerr << config.Usage;
286 }
287 exit(1);
288 }
289 }
290
291 for (int nonOpt = 0; nonOpt < parse.nonOptionsCount(); ++nonOpt)
292 {
293 VTKM_LOG_S(vtkm::cont::LogLevel::Info,
294 "Unknown argument to Initialize: " << parse.nonOption(nonOpt) << "\n");
295 if ((opts & InitializeOptions::ErrorOnBadArgument) != InitializeOptions::None)
296 {
297 std::cerr << "Unknown argument: " << parse.nonOption(nonOpt) << std::endl;
298 if ((opts & InitializeOptions::AddHelp) != InitializeOptions::None)
299 {
300 std::cerr << config.Usage;
301 }
302 exit(1);
303 }
304 }
305
306 // Now go back through the arg list and remove anything that is not in the list of
307 // unknown options or non-option arguments.
308 int destArg = 1;
309 for (int srcArg = 1; srcArg < argc; ++srcArg)
310 {
311 std::string thisArg{ argv[srcArg] };
312 bool copyArg = false;
313
314 // Special case: "--" gets removed by optionparser but should be passed.
315 if (thisArg == "--")
316 {
317 copyArg = true;
318 }
319 for (const opt::Option* opt = options[UNKNOWN]; !copyArg && opt != nullptr; opt = opt->next())
320 {
321 if (thisArg == opt->name)
322 {
323 copyArg = true;
324 }
325 if ((opt->arg != nullptr) && (thisArg == opt->arg))
326 {
327 copyArg = true;
328 }
329 // Special case: optionparser sometimes removes a single "-" from an option
330 if (thisArg.substr(1) == opt->name)
331 {
332 copyArg = true;
333 }
334 }
335 for (int nonOpt = 0; !copyArg && nonOpt < parse.nonOptionsCount(); ++nonOpt)
336 {
337 if (thisArg == parse.nonOption(nonOpt))
338 {
339 copyArg = true;
340 }
341 }
342 if (copyArg)
343 {
344 if (destArg != srcArg)
345 {
346 argv[destArg] = argv[srcArg];
347 }
348 ++destArg;
349 }
350 }
351 argc = destArg;
352 }
353
354 return config;
355 }
356
357 VTKM_CONT
Initialize()358 InitializeResult Initialize()
359 {
360 vtkm::cont::InitLogging();
361 return InitializeResult{};
362 }
363 }
364 } // end namespace vtkm::cont
365