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