Set compile options for gInterpreter

The task at hand is to evaluate a function that is only known at run time with help of ROOT.

The below example can be called with ./main "return 1000*a + b;" and it creates a lambda [](int a, int b) { return 1000*a + b; } and calls it with arguments 42, 23.

#include "TInterpreter.h"
#include "TInterpreterValue.h"
#include <functional>
#include <iostream>

// inspired from https://stackoverflow.com/a/28747100
class Functor {
  using FuncType = std::function<int(int, int)>;
  FuncType m_f;

public:
  Functor(FuncType f) : m_f{std::move(f)} {}

  int operator()(int a, int b) { return m_f(a, b); }
};

int main(int argc, char **argv) {
  Functor *runtime_functor(nullptr);
  Functor *compiletime_functor(nullptr);
  TInterpreterValue *tiv = gInterpreter->CreateTemporary();

  // yes, I let that leak but don't care for the sake of this example
  compiletime_functor = new Functor([](int a, int b) { return a + b; });

  std::string expr("Functor([](int a, int b) {");
  expr += argv[1];
  expr += "})";

  // TODO: put this into a header
  gInterpreter->Declare("class Functor {                                       "
                        "  using FuncType = std::function<int(int, int)>;      "
                        "  FuncType m_f;                                       "
                        "                                                      "
                        "public:                                               "
                        "  Functor(FuncType f) : m_f{std::move(f)} {}          "
                        "                                                      "
                        "  int operator()(int a, int b) { return m_f(a, b); }  "
                        "}; ");

  std::cout << "evaluating lambda: " << expr << std::endl;
  gInterpreter->Evaluate(expr.c_str(), *tiv);

  runtime_functor = (Functor *)tiv->GetAsPointer();

  std::cout << "evaluating compiletime lambda with arguments 42, 23"
            << std::endl;

  std::cout << (*compiletime_functor)(42, 23) << std::endl;

  std::cout << "evaluating runtime lambda with arguments 42, 23" << std::endl;

  std::cout << (*runtime_functor)(42, 23) << std::endl;

  return 0;
}

This code works okay, but I realised that the Evaluate call does not lead to diagnostics:

./main "
int k;
return k;"

This would normally with g++ lead to the warning warning: ‘k’ is used uninitialized in this function [-Wuninitialized] but doesn’t here with the gInterpreter.

I saw I can set the include paths for the gInterpreter with gInterpreter->AddIncludePath(...) and inspect its content with GetIncludePath. But I’m wondering, can I set other compiler options like warnings, optimisations, or other -f -m settings for the gInterpreter? (Maybe as background info: does Evaluate actually lead to compilation by cling?)

side note: For similar’ish use cases I used gInterpreter->LoadMacro(filename.cpp+O) and steered the behaviour with gSystem->SetFlagsOpt but would like to avoid the boiler plate of creating and cleaning up temporary files.

PPS: this is an MWE for https://gitlab.cern.ch/lhcb/LHCb/merge_requests/1302

used gInterpreter->LoadMacro(filename.cpp+O) and steered the behaviour with gSystem->SetFlagsOpt but would like to avoid the boiler plate of creating and cleaning up temporary files.

As a side note, ACLiC can delete the generated shared library. For example:

gSystem->CompileMacro("filename.cpp","O");  // missing the default 'k' option which request keeping the library at the end

Yes, Declare and Evaluate lead to a compilation by Cling.

As a side note, ACLiC can delete the generated shared library. For example:

gSystem->CompileMacro("filename.cpp","O");  // missing the default 'k' option which request keeping the library at the end

Thanks, that may clean up the generated shared library, but still leaves me with creating the .cpp file and cleaning these up, (including abnormal program terminations, knowing there are vastly different setups for /tmp cleanup). So if we can set -O, -f, -m, -D for Evaluate and Declare we’d prefer that.

ROOT uses a PCH, which is generated for a given -m etc. We cannot change that “in the middle of the translation unit” (a cling process lifetime is one clang translation unit). So no luck for -m.

A restricted set of -f might (e.g. the value of -ffast-math changes symbols and shouldn’t be changed from that used by cling, which is the one used when building ROOT), and -D will work. These options are parsed at initialization time; you can add to them by setting the environment variable EXTRA_CLING_ARGS.

I should maybe specify priorities. -O is probably the most important for what we were planning (at least when Florian was comparing “syscall to gcc” with “just use gInterpreter” he saw a runtime penalty of 30x to 100x - we suspect cling used -O0 in the ROOT version we were testing on), followed by -D. The others … not sure if they are correct already (we’d probably want the same as with what ROOT got built anyway).

But we were throwing around the overall design a few times in the last days anyway and my reservations against temporary text files got overruled by “we want to cache the compiled code from runtime to runtime, and possibly even allow deploying them”. So tempfiles turned from annoyance into feature. So we probably won’t use Evaluate and Declare anyway.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.