guile-devel
[Top][All Lists]
Advanced

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

Stack unwinding for C code


From: Marius Vollmer
Subject: Stack unwinding for C code
Date: Fri, 26 Dec 2003 22:36:31 +0100
User-agent: Gnus/5.1002 (Gnus v5.10.2) Emacs/21.3 (gnu/linux)

Hi,

I'm slowly making progress with replacing the gh_ interface and I
found that we first need to have good support for dealing with
non-local exits in the middle of allocating some temporary data
structures in order to prevent memory leaks.

We could either prevent non-local exits for a region of code, or allow
them and register cleanup actions.  Preventing them is difficult since
since you either need to avoid calling almost all libguile functions
in that region (which is very restrictive) or make sure that no error
actually happens (which is very difficult in a multi-threaded
program).

The proposal below therefore addresses the registerin of cleanup
actions, much like dynwind but in a more C friendly way.

Comments?  (I hope to implement most of it in the next days...)


Unwinding C call frames
-----------------------

Error reporting differes between C and Scheme: C functions usually
return some kind of indication to their calling function when an error
occured.  Scheme functions (including functions written in C that
follow the Scheme conventions) on the other hand usually do not return
to their caller in case of an error but instead transfer control to
some handler 'up the stack'.

Such a non-local transfer of control is called "unwinding the stack"
(and is implemented with longjmp).

Sometimes, it is necessary to perform cleanups when the unwinding
happens.  Frequently, dynamically allocated temporary data structures
need to be deallocated, for example.

Scheme code can use 'dynwind' to register such cleanup actions.  C
code could use it as well, but it would require a lot of code that
would be unnatural for C.  Therefore, Guile offers a facility that is
better suited for C code.

These frames also inhibit continuations (as explained below).  Thus,
you might want to use frames even when you don't have any cleanup
actions to perform.  Also, the label that appears in a backtrace is
useful on it own as well.

Here is an example.  Reference documentation is below.

  /* Suppose there is a function called FOO in some library that you
     would like to make available to Scheme code (or to C code that
     follows the Scheme conventions).

     FOO takes two C strings and returns a new string.  When an error
     has occured in FOO, it returns NULL.
  */

  char *foo (char *s1, char *s2);

  /* SCM_FOO interfaces the C function FOO to the Scheme way of life.
     It takes care to free up all temporary strings in the case of
     non-local exits.  */

  SCM
  scm_foo (SCM s1, SCM s2)
  {
    char *c_s1, *c_s2, *c_res;

    scm_begin_frame ("foo");
      {
        c_s1 = scm_to_string (s1);
        scm_on_unwind (free, s1, 1);

        c_s2 = scm_to_string (s2);
        scm_on_unwind (free, s2, 1);

        c_res = foo (c_s1, c_s2);
        if (c_res == NULL)
          scm_memory_error ("foo");
      }
    scm_end_frame ();

    return scm_from_string (res, 1);
  }


- C Function: void scm_begin_frame (const char *label)

  Starts a new frame and makes it the 'current' one.  The frame will
  be 'non-continueable', see below.

  The given LABEL will appear in backtraces and error messages.

  The frame is ended either implicitely when a non-local exit happens,
  or explicitely with scm_end_frame.

- C Function: void scm_on_unwind (void (*func)(void *), void *data,
                                  int explicit)

  Arranges for FUNC to be called with DATA as its arguments when the
  current frame ends implicitely.  When EXPLICITElY is non-zero, FUNC
  is also called when the frame ends explicitely.

- C Function: void scm_end_frame ()

  End the current frame explicitely and make the previous frame
  current.


Continuations and frames
------------------------

In addition to unwinding the stack, Scheme can also 'rewind' it.  This
can happen when a continuation is called.  When a stack is rewound,
call frames are added back to it.  This can even happen multiple times
so that some code might find that a function returns more than once.
This is against the expections of most C code.  For this reason, the
frames explained above do not allow the construction or invokation of
continuations while they are active.  Thus, a frame ends exactly once,
either implicetely or explicitely.

When you do want to allow continuations, you can start a frame with
scm_begin_continuable_frame instead of with scm_begin_frame.  In
addition to unwind actions, you can also register rewind actions for
such frames with scm_on_rewind.

- C Function: void scm_begin_frame (const char *label)

  Starts a new frame and makes it the 'current' one.  The frame will
  be 'continueable', that is, it allows continuations to be created
  and invoked while it is active.

- C Function: void scm_on_rewind (void (*func)(void *), void *data,
                                  int explicit)

  Arrange for FUNC to be called with DATA as its argument when the
  current frame is restarted by rewinding the stack.  When EXPLICIT is
  non-zero, FUNC is called immediately as well.


Related stuff
-------------

- C Function: void scm_c_define_proc (const char *name,
                                      int req, int opt, int restp,
                                      SCM (*proc) (...))

  The same as scm_c_define_gsubr but automatically begins and ends a
  non-continuable frame around the call to PROC.  The label of the
  frame is NAME.


Implementation
--------------

The frames stuff above can and probably should be the basis for all
the dynwind business of Guile, with scm_dynwind, for example, being
implemented using scm_begin_continuable_frame, scm_on_rewind,
scm_on_unwind, etc.

Also, the frames stored by the debugging evaluator could be combined
with this.

All data for frames could ba allocated on the stack, but the above API
does not allow this.  This has been done to achieve easy binary
compatibility and a very clean and simple API.


-- 
GPG: D5D4E405 - 2F9B BCCC 8527 692A 04E3  331E FAF8 226A D5D4 E405




reply via email to

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