[gambit-list] Anyone else working in a schemey wrapper for c structs/unions/types?

Estevo euccastro at gmail.com
Fri Dec 27 11:27:04 EST 2013


Then again, it's hard to offer this table library (or whatever replaces it)
with similar portability properties as those of gambit itself, and I don't
know how well would that work in a Gambit running on multiple native
threads.  The ___FOREIGN_DEP approach looks much more robust in this
respect, since it just relies on the existing garbage collection logic in
Gambit.


On Fri, Dec 27, 2013 at 5:27 AM, Estevo <euccastro at gmail.com> wrote:

> It's not true that the experiment below[1] runs valgrind-clean.  I must
> have forgotten to compile with -g or something.  Calling a `c-define`d
> function from a release functions gives me this:
>
>
> https://mercure.iro.umontreal.ca/pipermail/gambit-list/2013-December/007346.html
>
> Anyway, I worked out a proof of concept where the 'table' is in C:
>
> https://github.com/euccastro/refcount-hacks
>
> I think this will do the trick.
>
> [1] (I sent the reply below only to Mikael, by mistake.)
>
>
> ---------- Forwarded message ----------
> From: Estevo <euccastro at gmail.com>
> Date: Thu, Dec 26, 2013 at 2:53 PM
> Subject: Re: [gambit-list] Anyone else working in a schemey wrapper for c
> structs/unions/types?
> To: Mikael <mikael.rcv at gmail.com>
>
>
>
> https://github.com/feeley/gambit/pull/61 - for a quick proof of concept,
>> that's a very neat use of the ffi type!!!
>>
>
> Thanks!  I tried the approach you suggested and the code is much simpler,
> at least for this particular purpose.  There are some dangling questions
> left, that I explain below in more detail.
>
>
> So you tried to somehow use wills to force GC order? Without having
>> studied this, I guess that was not what they were intended for in the first
>> place and it makes good sense that that did not work out well.
>>
>
> I completely agree.
>
>>
>>> Once user code gets hold of A, pure Scheme code can't really know when
>>> it's OK to let go of that vector.  It's the same problem.  Of course you
>>> can keep a reference forever, but you don't want to hold on to unneeded
>>> memory either.
>>>
>>
>> You should be able to work that out and question is just how heavy it
>> would be implementation-wise to do, no??
>>
>> If not why not?
>>
>
> There is no way in pure Scheme to detect when an object has been
> reclaimed.  This follows from the unlimited extent semantics mandated in
> the Scheme Reports.  From the perspective of Scheme code, objects are never
> deleted.  Gambit doesn't violate this, and that is fine.
>
>
>> But these restrictions need not be showstoppers.  A scheme that might
>>> work:
>>>  - on initalization, create an ___alloc_rc'ed table[1] and assign a
>>> global C pointer to it
>>>  - whenever you create a dependent foreign pointer F, store in the table
>>> a reference to its dependent object D, keyed by the address of the F's C
>>> pointer, and
>>>  - set a release function for F that will clear that entry in the table.
>>>
>>
>> Why do you need a "table" (by table here you mean resizable vector)?
>>
>
> No, I mean the type of object that (make-table) returns.  I need
>  - to keep strong references to the original object in some place that the
> garbage collector will scan,
>  - to be able to retrieve these references by key (in particular, by
> address of the C object to which they point), in order to remove them when
> the dependent pointer is finalized.
>
>
>> Nice!
>>
>> Gambit's builtin functionality for this is fully sufficient at Gambit's
>> level of abstraction over the underlying system, and indeed there's plenty
>> of space for higher-level abstractions that more in this area.
>>
>
> Indeed!  I should add that I don't want to hide Gambit's standard ffi
> facilities.  I want to hide my own hacks so what the user gets works
> seamlessly with those facilities, should they need more control than what
> my library provides.  That's why I want to give the user bare foreign
> objects and no scheme wrappers over them.
>
>
>> So the totality of what you are looking to provide is
>>
>>  a) the c-struct form which is analogous to define-type with
>>
>>  b) Scheme-like automatic dependency GC-reference handling between c
>> structure instances as discussed here
>>
>> ?
>>
>
> Yes, at least in a first step.  After that is working I'm thinking of
> doing something similar for arrays of structs/unions/types, perhaps with a
> vector-like interface.  That is, if you allocate an array of structures the
> array will stick around for exactly as long as you hold a pointer to any of
> its members.
>
> Here's a proof of concept that runs valgrind-clean and prints the expected
> output in an unmodified Gambit:
>
>     (c-declare "typedef struct { int x; int y; } point;")
>
>     (define no-err ((c-lambda () scheme-object "___result =
> ___FIX(___NO_ERR);")))
>
>     (define dependencies (make-table))
>
>     (c-define (release-dependent p) ((pointer void)) scheme-object
> "release_dependent" ""
>       (if p
>         (begin
>           (println "resetting " (foreign-address p))
>           (table-set! dependencies (foreign-address p)))
>         (println "Null pointer?"))
>       no-err)
>
>     (c-define-type point (type "point" (point point*)))
>     (c-define-type dependent-point (type "point" (point* point)
> "release_dependent"))
>
>     (define (make-dependent-point dependency)
>       (let ((p
>               ((c-lambda ()
>                          dependent-point
>                      "___result_voidstar =
> ___EXT(___alloc_rc)(sizeof(point));
>                      /* I would really set these using accessors. */
>                      ((point*)___result_voidstar)->x = 1;
>                      ((point*)___result_voidstar)->y = 2;"))))
>         (println "setting " (foreign-address p))
>         (table-set! dependencies (foreign-address p) dependency)
>         p))
>
>     (define point-x
>       (c-lambda (point)
>                 int
>         "___result = ((point*)___arg1_voidstar)->x;"))
>     (define point-y
>       (c-lambda (point)
>                 int
>         "___result = ((point*)___arg1_voidstar)->y;"))
>
>
>     (define (point->string p)
>       (string-append
>         "(point "
>         (number->string (point-x p))
>
>         " "
>         (number->string (point-y p))
>         ")"))
>
>     (define d (cons 'depended 'upon))
>     (make-will d (lambda (x) (write x) (println " checking out")))
>     (define p (make-dependent-point d))
>     (println "p is " (point->string p))
>     (println "forgetting global d; its will should not get executed yet")
>     (set! d #f)
>     (##gc)
>     (##gc)
>     (##gc)
>     (println "letting go of p; this should kill d in two garbage
> collections at most")
>     (set! p #f)
>     (println "first garbage collection")
>     ; The first collection triggers the finalization of p, which deletes
> it from
>     ; the table.  But by the time that happens, the table has already been
>     ; scanned, and d has been found there.
>     (##gc)
>     (println "second garbage collection")
>     ; In the second collection d will not be reached anymore, so it's
> reclaimed.
>     (##gc)
>     (println "done.")
>
> This is not the end of the story, though.  Some consideration must be
> added for transitive dependencies and for several foreign objects depending
> on the same root.  I don't think there will be any blockers here.  I'll
> give it a go this afternoon, and if I get something solid I'll withdraw my
> pull request.
>
> Thanks again for bouncing ideas!
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailman.iro.umontreal.ca/pipermail/gambit-list/attachments/20131227/0f86d267/attachment.htm>


More information about the Gambit-list mailing list