guile-user
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Need help embedding Guile


From: Mikael Djurfeldt
Subject: Re: Need help embedding Guile
Date: Wed, 22 Dec 2021 18:37:59 +0100

Hi,

I think you should give up the idea to GC collect all objects. Due to the
GC being of the conservative type, that can't be guaranteed to happen. Any
attempt to wait until all objects are collected runs the risk of hanging
indefinitely.

Here's a suggestion for a less "heavy-handed" approach than to kill a child
process. Take it with a grain of salt since I'm not entirely up-to-date
with Guile:

First, skip Guile finalizers. You could then let the responsibility of
finalizing and freeing the C++ objects be shared between Guile and your C++
application. For example, the C++ application could have a doubly linked
list of the C++ objects. When Guile collects an object, make it unlink it
from the C++ list. Then, when you want to enter your second phase, you can
go through the list, which now only contains the objects not yet collected,
and finalize them. This creates the following problem: What if some Guile
code runs *after* you have finalized your remnant objects? Then, these
objects would be interpreted as live, but they are, in fact, dead and could
cause a segmentation fault. So, you either need to make sure that no Guile
code runs after this point (maybe somewhat ugly, since it leaves the Guile
data structures in an inconsistent state), or only allow Guile to
deallocate the first level C++ data structure. That data structure could
then, e.g., contain a flag indicating whether the object is dead or alive.

All of this indicates that it could be nice to have some kind of Guile
shutdown call in the C API. Such a shutdown call could go through live
objects and free them.

Best regards,
Mikael

On Wed, Dec 22, 2021 at 12:47 PM Dimitris Papavasiliou <
dpapavas@protonmail.ch> wrote:

> Hi Olivier,
>
> Thanks for chiming in!
>
> On Wednesday, December 22nd, 2021 at 12:39 AM, Olivier Dion wrote:
>
> > From this description, what I understand is that you want to use Scheme
> > as a configuration file for batching the operations to be done in a
> > second phase in C++.
>
> From an architectural point of view, the situation is probably similar
> to simple configuration parsing, as you say.  In practice though, the
> scope of Guile's role won't be nearly as limited as what this might
> imply.
>
> In a way, what I'm working on, can be described as a "compiler" for
> geometry.  That is to say, it reads in the description of geometry in
> some language and then evaluates it and dumps it in a format that can
> be used for e.g. 3D printing.  Guile (amongst other options) supplies
> the language frontend.  For example, one might invoke it on the shell
> like this:
>
> $ compile hollow_cube.scm
>
> Here, `hollow_cube.scm' would contain, say:
>
> (write-stl
>   "hollow_cube.stl"
>   (difference
>     (cube 2 2 2)
>     (cube 1 1 1)))
>
> When the `cube' procedure is evaluated, it creates a node in the C++
> backend that evaluates to the geometry of a cube.  Similarly
> `difference' creates a node, with edges to the two cubes, which when
> evaluated computes their boolean difference and so on.  Of course, in
> this instance, the Scheme code amounts to little more than what might
> be termed a configuration file, but imagine the code to create a
> complex mechanical part and the situation is (conceptually) quite
> different.
>
> Creating the complete graph before evaluation begins in the second
> phase is probably not necessary (nodes could be evaluated as they're
> created), but it creates the opportunity for certain optimizations
> (like dead code elimination for instance).  This makes some sort of
> forcing/ensuring that Guile has terminated desirable.
>
> > However, I failed to see why you need to finalize these objects
> > since you're going to use them in your second phase?
>
> Foreign objects currently come in two categories:
>
> 1. Complete geometric operations such as `cube' and `difference'
>    above.  These are allocated on the C++ side and a so-called "smart
>    pointer" (shared_ptr) is exported to Scheme.  Failure to finalize
>    this retains a reference on the C++ side, which would prevent
>    destroying the operation.  Since these can get quite large in terms
>    of memory, destroying them after they're no longer needed can be
>    essential.
>
> 2. "Complex" arguments to the above operations such as
>    transforamtions, planes, points, etc. which are C++ classes, for
>    which the destructor needs to be called in one way or the other.
>    These are generally passed by value to the operations and can
>    therefore be freed once the first phase is complete.
>
> > One way I think of would be to fork the process and create your C++
> > objects in a shared memory area between the parent and the child. Once
> > Guile is done reading your inputs, the child process dies and all its
> > memory is reclaimed by the OS.
>
> I have considered that but, besides the fact that it seems a bit
> heavy-handed, it still won't allow me to make sure the C++ objects are
> properly finalized.  This creates two problems: for one, some object
> might need finalization that's more than just freeing memory and I'd
> have to make sure that this isn't the case for each specific type
> (which typically come from external libraries).  And for another,
> these would show up as leaks in tools such as Valgrind, which would
> make checking for memory bugs difficult.
>
> One idea would be to simply call `scm_gc()' and `scm_run_finalizers()'
> until the latter returns 0.  As far as I can see, this should ensure
> all finalizers are called, assumming no references to any foreign
> objects remain, but I see no way of ensuring the latter short of
> process termination...
>
> Dimitris
>
>
>


reply via email to

[Prev in Thread] Current Thread [Next in Thread]