Augmenting Cling with Custom Functions

I had asked about building a custom interpreter using root in this post .

I was pointed to this cling-demo as a possible solution.

I downloaded and built cling and built the cling-demo. However, what it does is not what I am looking for. The cling-demo is an executable that uses the interpreter functionality but cannot interpret a script file that is passed to it as an argument.

I am trying to build an ‘enhanced’ interpreter that integrates additional proprietary functionality into cling which can then be used to interpret a source file which does not need to be compiled.

I’m starting this new thread to see if I can explain better and build what I need using Cling (or Root).

I hope that someone familiar with both Cint and Cling/Root can tell me if what I am looking for is possible in Cling/Root (and how). Cint worked great for us and we wouldn’t be looking for another solution had support for Cint not ceased for recent releases of linux.

To reiterate, I just need an augmented C++ interpreter which can also execute some other proprietary instrumentation functions that I have.

More given below with a very trivial code example.

Thank you.

_ROOT Version: v6.26.10
_Platform: Ubuntu 22.04
_Compiler: g++ 11.3.0

I have a source file mysrc.c which implements this function myfunc1() that returns the product of the two input parameters:

int myfunc1(int x, int y)
{
   return (x * y);
}

I’d like to integrate myfunc1() into cling/root so that the new cling/root can interpret a script file myscript.c which calls myfunc1(). The aim is to not need any compilation for myscript.c to be able to use the functions that it invokes.

/* File myscript.c  */

#include <stdio.h>

main()
{
  int x = myfunc1(3, 4);
  printf("%d\n", x);
}

To put it in Cint perspective, I could accomplish the above as follows with Cint:

  1. Declare the function prototype of myfunc1() in myhdrs.h
  2. Compile mysrc.c to get mysrc.o
  3. Define a makecint makefile with the following command -
makecint  -mk  mkfile_mylib  -o  mycint  -H  myhdrs.h  -l mysrc.o

The above creates a makefile called mkfile_mylib

When ‘make -f mkfile_mylib’ is executed, it creates the augmented/custom Cint mycint.

Then, mycint can be used by itself (no arguments) and it starts the augmented
interpreter which can also execute myfunc1() as follows (“$>” is the shell prompt):

$> 
$> mycint
mycint>
mycint> {myfunc1(2, 3);}
(int)6
mycint>
mycint> q
$>

or I could pass the script file myscript.c to mycint as follows:

$> 
$> mycint myscript.c
$> 12
$>

Is it possible to accomplish this with Cling (or root)? If so, how?

I guess @vvassilev and @Axel can help you.

Yes, I hope one of the experts gives me some pointers. Axel N, Daniel B and Philippe C had provided invaluable guidance and workarounds when I built the custom cint many years back.

Vassil Vasisilev @vvassilev is the author of the cling demo you pointed out in your first post. I am sure he can help you.

Hi @salz-root, apologies for the delay. Have you tried to build cling standalone from GitHub - root-project/cling: The cling C++ interpreter ?

Then, instead of building a .o file you could build a .so file. Then from the cling prompt you can do:

cling> #include "myhdrs.h"
cling> .L myLib.so
cling> myfunc1(2, 3);

Is that the use case you had in mind or I am misinterpreting your project requirements?

Thank you @vvassilev for responding.

I have not built cling from the URL you have provided but I built it using the following directives from the cling_build_instructions page :

git clone http://root.cern/git/llvm.git src
cd src
git checkout cling-patches
cd tools
git clone http://root.cern/git/cling.git
git clone http://root.cern/git/clang.git
cd clang
git checkout cling-patches
cd ../..

mkdir build
cd build
cmake -DCMAKE_INSTALL_PREFIX=[Install Path] -DCMAKE_BUILD_TYPE=Release  ../../src
cmake --build .
cmake --build . --target install

My questions -

  1. Do I need to discard the cling I have built and rebuild it from the root-project/cling page that you’ve referred to?

  2. What is the command syntax to build the shared library mylib.so from mysrc.c?

Currently, I can load the source file into the cling that I have built and do the following:

[cling]$ #include "myhdrs.h"
[cling]$ .L mysrc.c
[cling]$ myfunc1(2, 3)
(int) 6
[cling]$

But we won’t be sharing the source files such as mysrc.c with anyone since they contain proprietary code. So we need to create libraries from our source and integrate them into cling beforehand. The end user just has to write a source script (myscript.c) which would invoke functions that are listed in myhdrs.h and pass the script file to the modified cling that already has the library of our proprietary functions built in. The end user would not be loading any libraries (e.g., with a “.L mylib.so” command in cling) to be able to execute/interpret their scripts that call our proprietary functions.

Please see my earlier post where I described how we achieved our project goals with cint.

As long as you can run cling then it does not matter where it came from.

gcc -o mylib.so -shared mysrc.c?

You just need to do .L mylib.so.

Thank you again @vvassilev.

I am finally able to build a shared library both with source code files and pre-compiled object files and load it into cling. This is a library built with multiple files calling functions defined in different places. So I am happy to have made it thus far. I can execute some simple functions that are built into the shared library.

However, I get segmentation fault the moment I try “anything of value”, i.e., anything that involves port initialization etc., which are essential for our tools. I cannot see anything in the stack dump that would point me in a helpful direction. The source code I am using is time tested and should not have segfaults. So, I am probably missing some vital piece of information.

What is PIC? And what does “-fPIC” do? While trying to build my library, I once got an error message that suggested I recompile with “-fPIC”. I have been unable to find what that is. But the error went away after I compiled as suggested.

Can you provide a minimal (obfuscated if you are more comfortable that way) reproducer where I can take a look?

Position independent code: Position-independent code - Wikipedia

I realized later that since the PIC error was seen while building my library, it had nothing to do with cling and was from g++. Sorry about that. But thanks for the Wiki pointer. The error was in relation to some open source AES encryption code we incorporate with the code we have written. I had not seen this error before while compiling with previous versions of g++. So I am not sure what causes the error now as the source code hasn’t changed (but I’ll leave it for now since I could build the library).

Regarding the segfault, I will see if I can reproduce it with something that I can share.
Meanwhile, I noticed something and wonder if that could be a cause.

If I have a file like mytest.c

/* File mytest.c  */

#include <stdio.h>

main()
{
  printf("\nHello from main()\n");
}

cint could execute it without any error/warning because it looks for main(). In other words, the following

cint  mytest.c

prints “Hello from main()”. The return type of main() can be omitted where main() is defined.

g++ only gives a warning but also compiles the above mytest.c and produces the same output as cint.

$>
$>  g++   -o  mytest   mytest.c
$>  ./mytest
$>
$>  Hello from main()
$>

With cling (and root), main() seems to have no significance and the function in mytest.c must be renamed to mytest() and explicitly defined to be of type void as follows:

void mytest()

so that

cling  mytest.c

works (i.e., prints “Hello from main()”).

But the more serious problem is that functions of implicit return type int like this:

mytest()
{
  printf("\nHello from main()\n");
  return 0;
}

or a function where the return type int is specified but which has no return statement like this

int mytest()
{
  printf("\nHello from main()\n");
  // return 0;
}

both result in a core dump when invoked with cling.

cling mytest.c

We probably have several functions of type int that may not have a return statement or have an implicit return type of int.

Could this be causing the segfault?

@vvassilev, is there any way to direct cling to execute a certain function when it starts up? A command line option or some config file parameter?

makecint had a flag (-B) to specify the initialization function while building the custom cint, so that when the custom cint was invoked, it executed the specified initialization function before the interpreter prompt was seen.

Thank you.

You can do .x filename.C which would load filename.C and run a function called filename.

@vvassilev,

In our setup, we do not use the customized interpreter (with our proprietary functions) by itself where one could do .x filename.c etc. after starting the interpreter. Expecting the end user to type anything extra will not work well.

Anything that a user does not need to know or do is built into the customized interpreter via incorporating the shared library (say, mylib.so) which is built with our functions. The intent is to allow the user to be able to execute their own scripts that invoke our proprietary functions to do their testing easily in addition to executing the test suites we provide.

Consider that (hypothetically) our end users want to test an automotive engine. They want to be able to change the RPM, change the amount of fuel injected, read the engine temperature under various conditions, etc. We provide API functions like set_rpm(), set_fuel_input(). They don’t know what happens inside set_rpm(), set_fuel_input(), etc. But they could write a test script rpm_test.c which would, say, increase the RPM progressively via set_rpm() till the engine explodes or something (bad design) :slight_smile: or shuts down safely.

With an interpreter that has set_rpm(), set_fuel_input() etc. loaded through the shared library (mylib.so), they can easily execute rpm_test.c and don’t need the source code for set_rpm().

The only file that a user would execute is rpm_test.c. However, before that, the initialization function I was asking about earlier would crank the engine and maybe do some other safety checks so that the setup is ready for rpm_test.c. The end users don’t need to concern themselves with the details of that.

We provide lots of tests like rpm_test.c in our test suite which assume that the initialization tasks have been performed. Unless initialization is successful, no test can be run.

Currently, I am able to do the following -

cling   -l   mylib.so   rpm_test.c

where rpm_test.c has a function rpm_test() that calls the initialization function built into mylib.so and then calls set_rpm(). But this involves modifying our pre-existing rpm_test.c to first call the initialization function before doing other RPM related testing. That would require a prohibitive amount of code change when nothing actually needs to change in rpm_test.c and other test scripts. We were able to use all those scripts with the custom cint which had a facility to specify an initialization function that would get executed upon invocation of the custom cint.

I hope this explains the requirements somewhat better. I have tried various other things to invoke the initialization function with cling without changing rpm_test.c but have not had success. Please let me know if there is a way to accomplish this with cling without massive code overhaul.

Also, could you please take a look at my earlier post (Jan 31) about the conditions under which I saw core dumps? I provided code examples (file mytest.c) that would help you reproduce the problem.

Thank you.

Hi @salz-root,

I wanted to follow-up on this topic. Did you make any progress at all?

The two immediate solutions that I can think of is, either (1) you modify cling itself to execute a startup script; or (2) you modify the cling demo to parse an external source file. If you also need an interactive prompt, option (1) seems more adequate and it should be simple enough to tweak cling to your requirement.

Given that, I think all that is missing is injecting a .x command before the REPL loop starts, no? A simple way, would be to modify UserInterface.cpp, and add the following before the while loop in line 148:

  {
    cling::Interpreter::CompilationResult result;
    m_MetaProcessor->process(".x /path/to/.cling_startup.cpp", result);
  }

where in /path/to/.cling_startup.cpp you would add your startup code. Such file might include any preprocessor directive, e.g. #include, top-level declarations, and an additional function void _cling_startup() that will be automatically called on startup.

Hope this helps in case you didn’t found your way through already.

Cheers,
J.

A bit related to the return type issues and cling: [cling] void macro should not return value · Issue #10895 · root-project/root · GitHub

Thank you @jalopezg .

I currently have a workaround that enables me to do most of what I need without modifying cling. That is true if I am executing a script but not when I use cling in the interactive format. The latter requires a #include directive before we can use any of the functions in our library that we load into cling.

I will experiment with building cling with your suggestion #1. The cling demo is not something that will work for our needs.

Could you please point me to where the cling prompt is defined? That is, when I invoke cling, I’d like the prompt to have a different string than “[cling]$”. Like the shell prompt PS1 in linux.

It would be great if cling could execute main() if it couldn’t find a function that has the same name as the source script that is passed to it to interpret. All our test scripts have main().

Thank you.

For the cling prompt, see the link provided in my previous post (cited); it’s literally 2 lines above the referenced line.

Cheers,
J.