Hacker News new | comments | show | ask | jobs | submit login
Inspector – A drop-anywhere C++ REPL (github.com)
194 points by Mic92 69 days ago | hide | past | web | favorite | 37 comments

Injecting a REPL into a given section of code is my favorite means of debugging and interactively developing. I've gotten into the habit of building up a class interactively within Emacs and re-evaluating files after they have been changed.

It feels a bit like test driven development, but much more concrete. When I don't really know what I want to build, or what is possible, it's a fun way to spike out some functionality.

Then after you see what sort of path could exist from your interactive session you can use the code as a scaffold and re-implement the functionality by writing a test and implementing the code to satisfy it.

How well does it work with class redefinition? Do your instances created with the old definition automatically update to become instances of the new definition, or do you need to recreate them?

Common Lisp goes to a lot of trouble to ensure old instances automatically become new instances - given the repl is a first class feature, the whole language is built around this concept. But with C++ I can't quite picture how it would work.

The variables and references need to be re-initialized. It would be absolutely amazing if instances updated themselves but I think only Lisps and Smalltalk are capable of what you are saying. Correct me if I am wrong.

It is not supported. You can only define each type once.

Even just knowing the variables in the Stack frame is useful also. The error pages for Django and pyramid have this. Also, at a previous job someone created a toolbar that had all of the relevant server variables in your session.

At a previous job, I built a server-side debugging tool which would record the requests that you made. For each request, you could see the HTTP request, the HTTP response, and events like SQL statements and log messages. (Sort of like Sentry.) There was also a button that re-ran the same request under a profiler.

Sadly the tool was never merged into mainline, so the feature branch has probaly rotten away by now.

Shlightly off-topic:

Maybe it's just me, but this is exactly why I'm not a fan of feature branches.

I do like to have local branches which sometimes serve as feature branches. But those are short-lived, so these either make it into mainline within a few days, or are deliberately marked as dead ends. (Or developed into a separate project, but this is very seldom and even then quickly moved into a new repository.)

A decent debugger will show all the local variables and their values.

Many variables are marked as optimized in debuggers - never in Inspector. This even happen sometimes to me, when compiler optimizations are disabled.

This comes at a good time as it's something I've been looking for. We have a physics engine and we want to allow users to write custom c++ functions that can be linked in at run-time. We'd like to allow our users to swap in their own custom integration or timestep methods for example.

As an aside, do you know if it is possible to use this to JIT C++ for CUDA kernels?

Are you imagining this running inside a cuda kernel? If yes, the problem with doing anything of the kind is that, even if you run your kernel with one warp and mask all but one thread, you still then need round trips to the cpu to do io, and you need to dynamically load code, both of which the gpu is quite bad about.

If no, it is probably doable if a bit hard to generate kernels on the fly.

> We have a physics engine and we want to allow users to write custom c++ functions that can be linked in at run-time. We'd like to allow our users to swap in their own custom integration or timestep methods for example.

Can't you achieve that with plain old dlopen etc? I suppose name mangling can be bit of pita if you want to have truly c++ interfaces instead of extern c ones.

Hopefully this is relevant, but you can certainly kick off nvcc -ptx in a shell to generate ptx files, then just load those in through driver calls.

I'm totally planning to do something like this for some bulk data processing I have in mind.

You probably want to use libcling directly instead.

From the source code, it looks like that's what this program is using as well.

Hence "directly", I read it as.

> As an aside, do you know if it is possible to use this to JIT C++ for CUDA kernels?

Are you aware of NVRTC?

Do people create stuff like this because they don't have access to good interactive debuggers? Visual Studio user here.

I tried visual studio once. It does not allow me to include all kinds of code in the edit-and-compile feature. Also the code I enter in a REPL, I usually don't want to see in my projects. Finally I miss the intermediate feedback of printing the values, when I have entered an expression.

This is an interesting question, but the phrasing sounds a little funny to me. Don't REPLs predate symbolic debuggers? As someone coming from the Lisp world, I've occasionally wondered about the converse: did people create interactive debuggers because their language didn't have a good REPL?

I've used Visual Studio before, and it's an impressive technical achievement, but for personal productivity, I'll take a good REPL any day -- as long as the language supports it.

That said, good REPLs contain a debugger, and good symbolic debuggers contain a REPL, so they're not entirely exclusive.

Hmm -- I wonder why they chose to not use Jupyter as an REPL "backend" (Jupyter calls them "kernels").

Cling (the underlying just-in-time compiler) supports this. However Cling is more a standalone REPL and has a different execution flow. Here the idea of Inspector is to be executed from within running, compiled programs

Thanks -- I wasn't aware that something like cling exists.

How does Inspector differentiate itself from embedding cling to an application? It's very interesting to me because I've been trying to get something like an IPython console / IPdb prompt inside Java using Jython for a long time, and I'd very much like to read more about some design decisions in that area.

To access local variables, as you can see in the example, Inspector uses libclang ahead of compile-time to generate code that captures all local variables where the REPL is invoked. In JVM you could probably use introspection to achieve the same at run-time. I also gave a brief presentation about the design: https://www.youtube.com/watch?v=Cl5RSlW6xAc

this exists! recently released: https://github.com/QuantStack/xeus-cling

Oh wow! Even a backend for Jupyter's interactive widgets!

Check out our blog post on the C++ backend to Jupyter widgets: https://blog.jupyter.org/interactive-workflows-for-c-with-ju...

Just reading up on xeus (nice name!) -- very exciting! Do you know it xeus happens to build on Windows 10?

I'm thinking about to try to use SWIG to create a JNI wrapper for it.

it does build on windows. we got it working with windows10 using visual studio 2015 build toolchain

Just noticed that Inspector seems to be related to cling, and cling has a built-in Jupyter kernel IIUC.

How is this better than just attaching GDB?

1. You can enter arbitrarily C++ code (gdb can only call some functions, it cannot even instantiate c++ classes) 2. Variables in Inspector's scope are never optimized away by the compiler 3. You can use it with default optimizations flags (In gdb you often want `-O0 -g`)

1. GDB can instantiate classes, but it's a bit wonky, I agree. You have to evaluate malloc manually and then cast the pointer to the right type and call the constructor. You can write python scripts to automate this.

2. That's good.

3. You can generally compile with -Og -g for faster execution while preserving enough of the program's structure to make sense of what's happening.

I can see how it can be useful though. Do you have to prebuild every cpp file, or just the one you put "#include INSPECTOR" in? Can you include inspector in more than one file?

You only need to prebuild files, where the include statement is included. Ideally this task would be also automatically done in future by sneaking into the build system.

On some university projects (to be delivered in C), as part of my edit-compile-debug loop, I was usually writing additional functions just to navigate the data structures using the GDB expression evaluator.

Very interesting. Does it require a C++17 compiler? Also is there any way to simply use a different way of interacting with the REPL? For instance over sockets rather than stdio.

The REPL is a C++17 just-in-time compiler as a library. The project can use a different compiler. Even C could be supported with some small modifications. It uses TCP sockets + simple json messages for IPC. The REPL prompt runs in a different process independent from the program running the REPL. That means, there could be different frontends added. It also enables multi-process/multi-threading support.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact