Vcsn  2.5.dev
Be Rational
translate.cc
Go to the documentation of this file.
2 
3 #include <chrono>
4 #include <fstream>
5 #include <memory>
6 #include <set>
7 #include <sstream>
8 #include <string>
9 #include <unistd.h> // getpid
10 
11 #include <boost/algorithm/string/case_conv.hpp>
12 #include <boost/filesystem.hpp>
13 
16 #include <lib/vcsn/dyn/type-ast.hh>
17 #include <lib/vcsn/misc/xltdl.hh>
18 
19 #include <vcsn/dyn/context.hh>
21 #include <vcsn/misc/escape.hh>
22 #include <vcsn/misc/indent.hh>
23 #include <vcsn/misc/regex.hh>
24 #include <vcsn/misc/signature.hh>
25 #include <vcsn/misc/stream.hh>
26 
27 namespace
28 {
30  int verbose = []{
31  auto res = 0;
32  auto cp = getenv("VCSN_VERBOSE");
33  std::istringstream is{cp ? cp : "0"};
34  is >> res;
35  return res;
36  }();
37 }
38 
39 namespace vcsn
40 {
41  namespace dyn
42  {
43 
44  jit_error::jit_error(const std::string& a, const std::string& what)
45  : std::runtime_error(what)
46  , assertions(a)
47  {}
48 
49  namespace
50  {
51 #define XGETENV(Name) xgetenv(#Name, Name)
52 
55  std::string
56  config(const std::string& var)
57  {
58  auto envvar = "VCSN_" + boost::algorithm::to_upper_copy(var);
59  if (auto cp = getenv(envvar.c_str()))
60  return cp;
61  else
62  return get_config()["configuration"][var].str();
63  }
64 
66  // http://stackoverflow.com/questions/4891006.
67  std::string expand_tilda(std::string res)
68  {
69  if (!res.empty() && res[0] == '~')
70  {
71  assert(res.size() == 1 || res[1] == '/');
72  auto home = xgetenv("HOME", xgetenv("USERPROFILE"));
73  char const *hdrive = getenv("HOMEDRIVE");
74  char const *hres = getenv("HOMERES");
75  if (!home.empty())
76  res.replace(0, 1, home);
77  else if (hdrive && hres)
78  res.replace(0, 1, std::string(hdrive) + hres);
79  else
80  res.replace(0, 1, xgetenv("VCSN_TMPDIR", "/tmp"));
81  }
82  return res;
83  }
84 
86  void ensure_parent_directory(const std::string& path)
87  {
88  boost::filesystem::path p(path);
89  boost::filesystem::create_directories(p.parent_path());
90  }
91 
93  std::string tmpname(std::string res)
94  {
95  res += ".";
96  res += std::to_string(getpid());
97  return res;
98  }
99 
100  struct translation
101  {
102  translation()
103  : printer_(os)
104  {}
105 
116  void print(const std::string& base)
117  {
118  ensure_parent_directory(base);
119  // For atomicity, generate a file with PID, then mv it
120  // (which is atomic on any decent OS/FS).
121  auto tmp = tmpname(base);
122  {
123  std::ofstream o{tmp + ".cc"};
124  printer_.print(o);
125  }
126  if (equal_files(tmp + ".cc", base + ".cc"))
127  boost::filesystem::remove(tmp + ".cc");
128  else
129  boost::filesystem::rename(tmp + ".cc", base + ".cc");
130  }
131 
134  void print_context(const std::string& ctx)
135  {
136  ast::parse_context(ctx)->accept(printer_);
137  }
138 
140  void print_type(const std::string& type)
141  {
142  ast::parse_type(type)->accept(printer_);
143  }
144 
148  void throw_compiler_errors(std::string cmd,
149  const std::string& err)
150  {
151  // Try to find assertion failures in the error log.
152  //
153  // $ g++-mp-4.9 -std=c++11 main.cc
154  // main.cc: In function 'int main()':
155  // main.cc:3:3: error: static assertion failed: foo
156  // static_assert(0, "foo");
157  // ^
158  // $ clang++-mp-3.5 -std=c++11 main.cc
159  // main.cc:3:3: error: static_assert failed "foo"
160  // static_assert(0, "foo");
161  // ^ ~
162  // 1 error generated.
163  //
164  // We don't try to catch the "error:" part, because
165  // vcsn-compile loves to add colors, so "error:" is actually
166  // cluttered with ANSI escapes for colors.
167  auto is = open_input_file(err);
168  static auto r1 = std::regex{"static assertion failed: (.*)$",
169  std::regex::extended};
170  static auto r2 = std::regex{"static_assert failed \"(.*)\"$",
171  std::regex::extended};
172  std::string line;
173  std::smatch smatch;
174  std::string assertions;
175  while (std::getline(*is, line))
176  if (std::regex_search(line, smatch, r1)
177  || std::regex_search(line, smatch, r2))
178  assertions += std::string(smatch[1]) + '\n';
179  if (verbose)
180  {
181  cmd += "\n compiler error messages:\n";
182  cmd += get_file_contents(err);
183  }
184  throw jit_error(assertions, " failed command:\n " + cmd);
185  }
186 
191  void cxx(std::string cmd, const std::string& tmp)
192  {
193  auto err = tmp + ".err";
194  if (getenv("VCSN_DEBUG"))
195  std::cerr << "run: " << cmd << '\n';
196  if (system((cmd + " 2>'" + err + "'").c_str()))
197  throw_compiler_errors(cmd, err);
198  else
199  {
200  // At least we should see the warnings.
201  std::ifstream log{err};
202  std::cerr << log.rdbuf();
203  // If the file is empty the previous instruction sets the state
204  // of cerr to bad. We clear the error state flag to be able to
205  // read from cerr afterwards.
206  std::cerr.clear();
207  boost::filesystem::remove(err);
208  }
209  }
210 
214  void cxx_compile(const std::string& base)
215  {
216  auto tmp = tmpname(base);
217  // We try to read the error message via a regexp below. So
218  // avoid translation (we once had "erreur" instead of "error").
219  auto cmd = (std::string{"LC_ALL=C"}
220  + " " + config("ccache")
221  + " " + config("cxx")
222  + " " + config("cxxflags")
223  + " " + config("cppflags")
224  + " -fPIC '" + base + ".cc' -c"
225  + " -o '" + tmp + ".o'");
226  cxx(cmd, tmp);
227  }
228 
232  void cxx_link(const std::string& base)
233  {
234  auto tmp = tmpname(base);
235  auto cmd = (std::string{"LC_ALL=C"}
236  + " " + config("cxx")
237  + " " + config("cxxflags")
238  + " " + config("ldflags")
239  + " -fPIC -lvcsn '" + tmp + ".o' -shared"
240  + " -o '" + tmp + ".so'"
241  + printer_.linkflags());
242  cxx(cmd, tmp);
243  }
244 
246  std::string plugindir() const
247  {
248  auto res = xgetenv("VCSN_PLUGINDIR",
249  xgetenv("VCSN_HOME", "~/.vcsn") + "/plugins");
250  res = expand_tilda(res);
251  return res + "/";
252  }
253 
256  std::string split(const std::string& s) const
257  {
258  auto res = std::string{};
259  const size_t size = 150;
260  for (unsigned i = 0; i < s.length(); i += size)
261  {
262  if (i)
263  res += '/';
264  res += s.substr(i, size);
265  }
266  return res;
267  }
268 
277  void jit(const std::string& base)
278  {
279  auto tmp = tmpname(base);
280  {
281  namespace chr = std::chrono;
282  using clock = chr::steady_clock;
283  auto start = clock::now();
284  static bool no_python = !!getenv("VCSN_NO_PYTHON");
285  if (no_python)
286  {
287  cxx_compile(base);
288  cxx_link(base);
289  boost::filesystem::rename(tmp + ".so", base + ".so");
290  // Upon success, remove the .o file, it is large (10x
291  // compared to the *.so on erebus using clang) and not
292  // required. However the debug symbols are in there, so
293  // when debugging, leave them!
294  if (!getenv("VCSN_DEBUG"))
295  boost::filesystem::remove(tmp + ".o");
296  }
297  else
298  {
299  auto cmd
300  = xgetenv("VCSN_COMPILE",
301  xgetenv("VCSN", "vcsn") + " compile");
302  auto linkflags = printer_.linkflags();
303  if (!linkflags.empty())
304  linkflags = " LDFLAGS+='" + linkflags + "'";
305  cxx(cmd + " -shared" + linkflags + " '" + base + ".cc'",
306  tmp);
307  }
308  auto d
309  = chr::duration_cast<chr::milliseconds>(clock::now() - start);
310  if (getenv("VCSN_TIME"))
311  {
312  std::ofstream{"/tmp/vcsn-compile.log",
313  std::ofstream::out | std::ofstream::app}
314  << d.count() << ", "
315  << (no_python ? "C++, " : "Py, ")
316  << '\'' << base.substr(plugindir().size()) << '\''
317  << '\n';
318  if (getenv("VCSN_TIME2"))
319  std::cerr << d.count() << "ms: " << base << '\n';
320  }
321  }
323  .global(true)
324  .ext()
325  .verbose(1 < verbose)
326  .open(base + ".so");
327  }
328 
330  void compile(const std::string& ctx)
331  {
332  printer_.header("vcsn/ctx/instantiate.hh");
333  auto base = plugindir() + "contexts/" + split(ctx);
334  os << "using ctx_t =" << incendl;
335  print_context(ctx);
336  os << ';' << decendl
337  <<
338  "\n"
339  "namespace vcsn\n"
340  "{\n"
341  " VCSN_CTX_INSTANTIATE(ctx_t);\n"
342  "}\n";
343  ;
344  print(base);
345  jit(base);
346  }
347 
349  void compile(const std::set<std::pair<std::string, signature>>& algos)
350  {
351  printer_.header("vcsn/misc/attributes.hh"); // ATTRIBUTE_USED
352  printer_.header("vcsn/dyn/name.hh"); // ssignature
353  printer_.header("vcsn/dyn/registries.hh");
354  for (const auto& algo: algos)
355  printer_.header_algo(algo.first);
356 
357  unsigned count = 0;
358  for (const auto& algo: algos)
359  {
360  os << iendl
361  << "// " << algo.first << '.';
362  std::string types;
363  bool first = true;
364  for (const auto& s: algo.second)
365  {
366  os << iendl;
367  std::string t = "t" + std::to_string(count) + "_t";
368  os << "using " << t << " =" << incendl;
369  print_type(s);
370  os << ';' << decendl;
371  types += (first ? "" : ", ") + t;
372  ++count;
373  first = false;
374  }
375  os <<
376  "\n"
377  "static bool vcsn_" << algo.first << " ATTRIBUTE_USED ="
378  << incendl
379  << "vcsn::dyn::detail::" << algo.first << "_register("
380  << incendl
381  << "vcsn::ssignature<" << types << ">(),"
382  << iendl
383  << "vcsn::dyn::detail::" << algo.first << "<" << types << ">"
384  << decendl
385  << ");" << decendl;
386  }
387 
388  // The first algo is the once that gives its name to the
389  // file to compile.
390  auto base = (plugindir()
391  + "algos/"
392  + begin(algos)->first + "/"
393  + split(begin(algos)->second.to_string()));
394  print(base);
395  jit(base);
396  }
397 
399  std::ostringstream os;
400  ast::context_printer printer_;
401  };
402  } // namespace detail
403 
404  void compile(const std::string& ctx)
405  {
406  translation translate;
407  translate.compile(ctx);
408  }
409 
410  void compile(const std::string& algo, const signature& sig)
411  {
412  translation translate;
413  std::set<std::pair<std::string, signature>> algos{{algo, sig}};
414  if (algo == "delay_automaton"
415  || algo == "is_synchronized")
416  {
417  algos.emplace("delay_automaton", sig);
418  algos.emplace("is_synchronized", sig);
419  }
420  translate.compile(algos);
421  }
422  } // namespace dyn
423 } // namespace vcsn
Request the set implementation (bool weights).
xlt_handle open(const std::string &s)
Definition: xltdl.cc:105
std::string type(const automaton &a)
The implementation type of a.
Definition: others.cc:238
std::shared_ptr< std::istream > open_input_file(const std::string &file)
Open file for reading and return its autoclosing stream.
Definition: stream.cc:174
xlt_advise & global(bool global)
Definition: xltdl.cc:46
std::ostream & print(const automaton &aut, std::ostream &out=std::cout, const std::string &format="default")
Print automaton a on out using format format.
Definition: print.hh:121
xlt_advise & ext()
Definition: xltdl.cc:55
std::ostream & iendl(std::ostream &o)
Print an end of line, then set the indentation.
Definition: indent.cc:49
bool equal_files(const std::string &fn1, const std::string &fn2)
Whether two files have exactly equal contents.
Definition: stream.cc:39
std::ostream & incendl(std::ostream &o)
Increment the indentation, print an end of line, and set the indentation.
Definition: indent.cc:54
weightset_mixin< detail::log_impl > log
Definition: fwd.hh:50
return res
Definition: multiply.hh:399
std::ostream & print_context(const context &ctx, std::ostream &o, const std::string &fmt)
Bridge (print).
Definition: print.hh:140
std::string expand_tilda(const std::string &res)
Expand initial "~" in res.
Definition: stream.cc:58
detail::config & get_config()
Get the configuration singleton.
size_t size(const ExpSet &rs, const typename ExpSet::value_t &r)
std::ostringstream os
The output stream: the corresponding C++ snippet to compile.
Definition: translate.cc:399
Signature of a function call.
Definition: signature.hh:15
std::shared_ptr< ast_node > parse_type(const std::string &type)
Parse a type, and return its AST.
std::string xgetenv(const std::string &var, const std::string &val="")
getenv(var) if defined, otherwise val.
Definition: stream.cc:210
std::shared_ptr< ast_node > parse_context(const std::string &ctx)
Parse a context, and return its AST.
ast::context_printer printer_
Definition: translate.cc:400
xlt_advise & verbose(int v)
Whether to report dlopen attempts.
Definition: xltdl.cc:63
auto out(const Aut &aut, state_t_of< Aut > s)
Indexes of visible transitions leaving state s.
Definition: automaton.hh:86
jit_error(const std::string &assert, const std::string &what)
Definition: translate.cc:44
Definition: a-star.hh:8
polynomial split(const expression &exp)
Break exp.
void compile(const std::string &ctx)
Compile, and load, a DSO with instantiations for ctx.
Definition: translate.cc:404
STL namespace.
std::string get_file_contents(const std::string &file)
Return the contents of file.
Definition: stream.cc:159
Indentation relative functions.
std::ostream & decendl(std::ostream &o)
Decrement the indentation, print an end of line, and set the indentation.
Definition: indent.cc:59
std::string assertions
If defined, static assertions that failed (ends with a eol).
Definition: translate.hh:17
std::string to_string(identities i)
Wrapper around operator<<.
Definition: identities.cc:38