[M3devel] function pointers and comparison to nil? mis-typed function pointers?

hendrik at topoi.pooq.com hendrik at topoi.pooq.com
Tue May 27 17:45:51 CEST 2008


On Sun, May 25, 2008 at 10:50:15PM -0500, Rodney M. Bates wrote:
> I think I can shed some light on this, having spent some time making
> m3gdb handle the various operations on nested procedures.  As for code
> that mixes M3 and C, I believe the following are true:
> 
> - Within M3 code only, the closure system works correctly in all cases.
>   This answers one of Jay's worries.

As long as procedure-entry code can be guaranteed never to start wit a 
word of one-bits.  We do have some influence on this code, though, and 
if necessary might be able to choose a different bit pattern on a 
specific platform.

> 
> - For values of M3 procedure/C function pointer that are top-level
>   (nonnested) procedures/functions, M3 and C code (generated by gcc,
>   at least) are interchangeable.  This answers another of Jay's worries.
>   This will cover that great majority of cases.

Yes.  And in many cases, we will know statically that this is the case.

> 
> - Standard C has no nested functions.  Gcc adds them as a language
>   extension.  Thus, only in gcc-compiled C code do we need to worry
>   about nested procedures/functions between languages.  (Do any other
>   C compilers exist that also have nested functions?)

Standard C has no nested function, and does not need closures.  As a 
result, Standard C can use simple pointers to represent function 
addresses.  The language extension retrofits closures using run-time 
code generation.  The way this is done (on the satck) is nonportable, 
and we'd like to avoid that nonportability.

The problem with seems to be just that you seem to want to use an 
address of a function as a function pointer, and that just doesn't have 
enough space in it to represent a closure.

But why do it that way?  Why not just let *all* Modula 3 functions be 
represented by closures?  Then you never have to test whether something 
is a closure, it just always is.  Top-level functions with no 
environment just use a null pointer as environment -- and never use it.

The only arguments for not doing this would seem to be 
(a) the waste of space, making functions a little larger than necessary, 
(b) and C compatibility.

Now (a) is probably a nonissue, since the vast majority of functions 
never have their addresses taken, are never passes as parameters to 
other procedures, and so forth.  So for the vast majority of functions, 
you simply never have to build the closure.

(b) might be a problem. The obvious trick is just to forbid passing to C 
a non-top-level function.  Since even the C programmers who devise 
interfaces with callbacks realise that just a functino pointer isn't 
enough, they usually provide a machanism for passing a void pointer to 
additional information the callback might need.  Nothing here puts 
Modula 3 at a disadvantage relative to C.  You can just use a top-level 
function and let the programmer provide it with whatever it needs.

But if it is deemed essential to provide actual single-pointer callback 
addresses, this can be done by using a built-in function to convert a 
procedure to a single-pointer callback.  This functin will have to be 
rewritten for each platform, and can allocate the necessary 
dynamically-genereated code on the heap (or, of course, on the stack, if 
possible on that platform).  As for portability, it's certianly no 
big deal to do this;  compared with writing a platform-dependent code 
generator (itself a requirement), this is not huge.

> - M3's normal runtime check that precludes assigning a nonlocal
>   procedure value will not detect a C-code-produced nonlocal value,
>   thus the environment could indeed have disappeared if the programmer
>   was not careful.  However, gcc-extended C's nested functions have
>   no protection against this bug when only C code is involved, so
>   perhaps not detecting it in mixed M3/C is to be expected.

We really can't protect against bugs in C code.  If we could prevent 
bugs in C, the market for it would be huge, and we'd be well advised to 
consider that our main business.

> 
> - C code that attempts to call a function pointer value that was
>   originally produced by M3 code as a nested procedure value will
>   almost certainly crash at the time of the call.  This is the only
>   case that presents a significant problem.  M3 code will not be
>   able to give a nested procedure as a callback to a C library.

Wherefor only in this case should we do run-time code generation.

It has been argued that we don't need to protect against C programmers 
going hog-wild and breaking their own code.  Such is the nature of C.

But, we can chack for some of it, it we are willing to go to the effort.  
The technique used in the CDC Algol 68 compiler long ago might even 
enable us to restrict the constraint on assigning nested procedures to 
variables by a suitable run-time check.

The CDC Algol 68 compiler had a trick for recognising expired scopes 
using the garbage collector.  Let's see if I can remember the details.  
It involved special treatment for procedures whose addresses are taken, 
and for the blocks that contain them.  When entering such a block at 
run-time, a word is allocated on the heap representing that scope.   It 
is filled with something relevant, such as the address if the stack 
frame for the block, and the stack frame also pointed to that scope-cell 
(as I'll describe it).  Taking the  address of a procedure involved 
building a closure that points to that scope-cell.  When leaving the 
block, the contents of the scope-cell is wiped to some recognisable 
invalid value.  When entering the procedure the scope-cell will 
still be around even if the scope is not.  The procedure (this is inside 
the procedure itself, not at the call) checks that the scope-cell has 
not been wiped, and therefore is still valid.  If valid, it contains the 
necessary environment information.  If not, break off execution.

-- hendrik



More information about the M3devel mailing list