• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
2 
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6 
7     http://www.apache.org/licenses/LICENSE-2.0
8 
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15 
16 // This module exports ParseFlagsFromEnvAndDieIfUnknown(), which allows other
17 // modules to parse flags from an environtment variable, or a file named by the
18 // environment variable.
19 
20 #include "tensorflow/compiler/xla/parse_flags_from_env.h"
21 
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include <memory>
27 #include <unordered_map>
28 #include <vector>
29 
30 #include "absl/strings/ascii.h"
31 #include "absl/strings/str_format.h"
32 #include "absl/strings/str_join.h"
33 #include "absl/types/span.h"
34 #include "tensorflow/compiler/xla/types.h"
35 #include "tensorflow/core/platform/logging.h"
36 #include "tensorflow/core/platform/macros.h"
37 #include "tensorflow/core/platform/mutex.h"
38 #include "tensorflow/core/platform/types.h"
39 #include "tensorflow/core/util/command_line_flags.h"
40 
41 namespace xla {
42 
43 static const char kWS[] = " \t\r\n";  // whitespace
44 
45 // The following struct represents an argv[]-style array, parsed
46 // from data gleaned from the environment.
47 //
48 // As usual, an anonymous namespace is advisable to avoid
49 // constructor/destructor collisions with other "private" types
50 // in the same named namespace.
51 namespace {
52 
53 // Functor which deletes objects by calling `free`.  Necessary to free strdup'ed
54 // strings created by AppendToEnvArgv.
55 struct FreeDeleter {
operator ()xla::__anon6a5d47500111::FreeDeleter56   void operator()(char* ptr) { free(ptr); }
57 };
58 
59 struct EnvArgv {
EnvArgvxla::__anon6a5d47500111::EnvArgv60   EnvArgv() : initialized(false), argc(0) {}
61   bool initialized;         // whether the other fields have been set.
62   int argc;                 // elements used in argv[]
63   std::vector<char*> argv;  // flag arguments parsed from environment string.
64   // saved values from argv[] to avoid leaks
65   std::vector<std::unique_ptr<char, FreeDeleter>> argv_save;
66 };
67 }  // anonymous namespace
68 
69 // Append the string s0[0, .., s0len-1] concatenated with s1[0, .., s1len-1] as
70 // a newly allocated nul-terminated string to the array *a.  If s0==nullptr, a
71 // nullptr is appended without increasing a->argc.
AppendToEnvArgv(const char * s0,size_t s0len,const char * s1,size_t s1len,EnvArgv * a)72 static void AppendToEnvArgv(const char* s0, size_t s0len, const char* s1,
73                             size_t s1len, EnvArgv* a) {
74   if (s0 == nullptr) {
75     a->argv.push_back(nullptr);
76     a->argv_save.push_back(nullptr);
77   } else {
78     string s = string(s0, s0len) + string(s1, s1len);
79     char* str = strdup(s.c_str());
80     a->argv.push_back(str);
81     a->argv_save.emplace_back(str);
82     a->argc++;
83   }
84 }
85 
86 // Like s.find_first_of(x, pos), but return s.size() when find_first_of() would
87 // return string::npos.  This avoids if-statements elsewhere.
FindFirstOf(const string & s,const char * x,size_t pos)88 static size_t FindFirstOf(const string& s, const char* x, size_t pos) {
89   size_t result = s.find_first_of(x, pos);
90   return result == string::npos ? s.size() : result;
91 }
92 
93 // Like s.find_first_not_of(x, pos), but return s.size() when
94 // find_first_not_of() would return string::npos.  This avoids if-statements
95 // elsewhere.
FindFirstNotOf(const string & s,const char * x,size_t pos)96 static size_t FindFirstNotOf(const string& s, const char* x, size_t pos) {
97   size_t result = s.find_first_not_of(x, pos);
98   return result == string::npos ? s.size() : result;
99 }
100 
101 // Given a string containing flags, parse them into the XLA command line flags.
102 // The parse is best effort, and gives up on the first syntax error.
ParseArgvFromString(const string & flag_str,EnvArgv * a)103 static void ParseArgvFromString(const string& flag_str, EnvArgv* a) {
104   size_t b = FindFirstNotOf(flag_str, kWS, 0);
105   while (b != flag_str.size() && flag_str[b] == '-') {
106     // b is the index of the start of a flag.
107     // Set e to the index just past the end of the flag.
108     size_t e = b;
109     while (e != flag_str.size() && isascii(flag_str[e]) &&
110            (strchr("-_", flag_str[e]) != nullptr ||
111             absl::ascii_isalnum(flag_str[e]))) {
112       e++;
113     }
114     if (e != flag_str.size() && flag_str[e] == '=' &&
115         e + 1 != flag_str.size() && strchr("'\"", flag_str[e + 1]) != nullptr) {
116       // A flag of the form  --flag="something in double or single quotes"
117       int c;
118       e++;  // point just past '='
119       size_t eflag = e;
120       char quote = flag_str[e];
121       e++;  // point just past quote
122       // Put in value the string with quotes removed.
123       string value;
124       for (; e != flag_str.size() && (c = flag_str[e]) != quote; e++) {
125         if (quote == '"' && c == '\\' && e + 1 != flag_str.size()) {
126           // Handle backslash in double quoted strings.  They are literal in
127           // single-quoted strings.
128           e++;
129           c = flag_str[e];
130         }
131         value += c;
132       }
133       if (e != flag_str.size()) {  // skip final " or '
134         e++;
135       }
136       AppendToEnvArgv(flag_str.data() + b, eflag - b, value.data(),
137                       value.size(), a);
138     } else {  // A flag without a quoted value.
139       e = FindFirstOf(flag_str, kWS, e);
140       AppendToEnvArgv(flag_str.data() + b, e - b, "", 0, a);
141     }
142     b = FindFirstNotOf(flag_str, kWS, e);
143   }
144 }
145 
146 // Call ParseArgvFromString(..., a) on a string derived from the setting of the
147 // environment variable `envvar`, or a file it points to.
SetArgvFromEnv(absl::string_view envvar,EnvArgv * a)148 static void SetArgvFromEnv(absl::string_view envvar, EnvArgv* a) {
149   if (!a->initialized) {
150     static const char kDummyArgv[] = "<argv[0]>";
151     AppendToEnvArgv(kDummyArgv, strlen(kDummyArgv), nullptr, 0,
152                     a);  // dummy argv[0]
153     const char* env = getenv(string(envvar).c_str());
154     if (env == nullptr || env[0] == '\0') {
155       // nothing
156     } else if (env[strspn(env, kWS)] == '-') {  // flags in env var value
157       ParseArgvFromString(env, a);
158     } else {  // assume it's a file name
159       FILE* fp = fopen(env, "r");
160       if (fp != nullptr) {
161         string str;
162         char buf[512];
163         int n;
164         while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) {
165           str.append(buf, n);
166         }
167         fclose(fp);
168         ParseArgvFromString(str, a);
169       } else {
170         LOG(QFATAL)
171             << "Could not open file \"" << env
172             << "\" to read flags for environment variable \"" << envvar
173             << "\".  (We assumed \"" << env
174             << "\" was a file name because it did not start with a \"--\".)";
175       }
176     }
177     AppendToEnvArgv(nullptr, 0, nullptr, 0, a);  // add trailing nullptr to *a.
178     a->initialized = true;
179   }
180 }
181 
182 // The simulated argv[] parsed from the environment, one for each different
183 // environment variable we've seen.
EnvArgvs()184 static std::unordered_map<string, EnvArgv>& EnvArgvs() {
185   static auto* env_argvs = new std::unordered_map<string, EnvArgv>();
186   return *env_argvs;
187 }
188 
189 // Used to protect accesses to env_argvs.
190 static tensorflow::mutex env_argv_mu(tensorflow::LINKER_INITIALIZED);
191 
ParseFlagsFromEnvAndDieIfUnknown(absl::string_view envvar,const std::vector<tensorflow::Flag> & flag_list)192 bool ParseFlagsFromEnvAndDieIfUnknown(
193     absl::string_view envvar, const std::vector<tensorflow::Flag>& flag_list) {
194   tensorflow::mutex_lock lock(env_argv_mu);
195   auto* env_argv = &EnvArgvs()[string(envvar)];
196   SetArgvFromEnv(envvar, env_argv);  // a no-op if already initialized
197 
198   if (VLOG_IS_ON(1)) {
199     VLOG(1) << "For env var " << envvar << " found arguments:";
200     for (int i = 0; i < env_argv->argc; i++) {
201       VLOG(1) << "  argv[" << i << "] = " << env_argv->argv[i];
202     }
203   }
204 
205   bool result =
206       tensorflow::Flags::Parse(&env_argv->argc, &env_argv->argv[0], flag_list);
207 
208   // There's always at least one unparsed argc, namely the fake argv[0].
209   if (result && env_argv->argc != 1) {
210     // Skip the first argv, which is the fake argv[0].
211     auto unknown_flags = absl::MakeSpan(env_argv->argv);
212     unknown_flags.remove_prefix(1);
213 
214     // Some flags are set on XLA_FLAGS, others on TF_XLA_FLAGS.  If we find an
215     // unrecognized flag, suggest the alternative.
216     string alternate_envvar;
217     if (envvar == "TF_XLA_FLAGS") {
218       alternate_envvar = "XLA_FLAGS";
219     } else if (envvar == "XLA_FLAGS") {
220       alternate_envvar = "TF_XLA_FLAGS";
221     }
222     string did_you_mean;
223     if (!alternate_envvar.empty()) {
224       did_you_mean = absl::StrFormat(
225           "\nPerhaps you meant to specify these on the %s envvar?",
226           alternate_envvar);
227     }
228 
229     LOG(QFATAL) << "Unknown flag" << (unknown_flags.size() > 1 ? "s" : "")
230                 << " in " << envvar << ": " << absl::StrJoin(unknown_flags, " ")
231                 << did_you_mean;
232     return false;
233   }
234   return result;
235 }
236 
237 // Testing only.
238 //
239 // Resets the env_argv struct so that subsequent calls to
240 // ParseFlagsFromEnvAndDieIfUnknown() will parse the environment variable (or
241 // the file it points to) anew, and set *pargc, and *pargv to point to the
242 // internal locations of the argc and argv constructed from the environment.
ResetFlagsFromEnvForTesting(absl::string_view envvar,int ** pargc,std::vector<char * > ** pargv)243 void ResetFlagsFromEnvForTesting(absl::string_view envvar, int** pargc,
244                                  std::vector<char*>** pargv) {
245   tensorflow::mutex_lock lock(env_argv_mu);
246   EnvArgvs().erase(string(envvar));
247   auto& env_argv = EnvArgvs()[string(envvar)];
248   *pargc = &env_argv.argc;
249   *pargv = &env_argv.argv;
250 }
251 
252 }  // namespace xla
253