add ffi/unsafe/collect-callback

The `ffi/unsafe/collect-callback` library exposes functionality
formerly only available via Racket's C interface, but implement
it for both Racket and RacketCs.
This commit is contained in:
Matthew Flatt 2018-07-29 13:30:02 -06:00
parent f8297f9c00
commit d9ec0705cf
12 changed files with 226 additions and 61 deletions

View File

@ -12,7 +12,7 @@
(define collection 'multi)
(define version "7.0.0.8")
(define version "7.0.0.9")
(define deps `("racket-lib"
["racket" #:version ,version]))

View File

@ -0,0 +1,94 @@
#lang scribble/doc
@(require "utils.rkt"
(for-label ffi/unsafe/collect-callback))
@title{Garbage Collection Callbacks}
@defmodule[ffi/unsafe/collect-callback]{The
@racketmodname[ffi/unsafe/collect-callback] library provides functions
to register constrained callbacks that are run just before and after a
garbage collection.}
@history[#:added "7.0.0.9"]
@defproc[(unsafe-add-collect-callbacks [pre (vectorof vector?)]
[post (vectorof vector?)])
any/c]{
Registers descriptions of foreign functions to be called just before
and just after a garbage collection. The foreign functions must not
allocate garbage-collected memory, and they are called in a way that
does not allocate, which is why @var{pre_desc} and @var{post_desc} are
function descriptions instead of thunks.
A description is a vector of vectors, where each of the inner vectors
describes a single call, and the calls are performed in sequence. Each
call vector starts with a symbol that indicates the protocol of the
foreign function to be called. The following protocols are supported:
@margin-note*{The apparently arbitrary and whimsical set of supported
protocols is enough to allow DrRacket to show a garbage-collection
icon.}
@itemlist[
@item{@racket['int->void] corresponds to @cpp{void (*)(int)}.}
@item{@racket['ptr_ptr_ptr->void] corresponds to @cpp{void
(*)(void*, void*, void*)}.}
@item{@racket['ptr_ptr->save] corresponds to @cpp{void* (*)(void*,
void*, void*)}, but the result is recored as the current ``save''
value. The current ``save'' value starts as @cpp{NULL}.}
@item{@racket['save!_ptr->void] corresponds to @cpp{void (*)(void*,
void*)}, but only if the current ``save'' value is not a @cpp{NULL}
pointer, and passing that pointer as the function's first argument
(so that only one additional argument is us from the description
vector).}
@item{@racket['ptr_ptr_ptr_int->void] corresponds to @cpp{void
(*)(void*, void*, void*, int)}.}
@item{@racket['ptr_ptr_float->void] corresponds to @cpp{void
(*)(void*, void*, float)}.}
@item{@racket['ptr_ptr_double->void] corresponds to @cpp{void
(*)(void*, void*, double)}.}
@item{@racket['ptr_ptr_ptr_int_int_int_int_int_int_int_int_int->void]
corresponds to @cpp{void (*)(void*, void*, void*, int, int, int, int,
int, int, int, int, int)}.}
@item{@racket['osapi_ptr_int->void] corresponds to @cpp{void
(*)(void*, int)}, but using the stdcall calling convention
on Windows.}
@item{@racket['osapi_ptr_ptr->void] corresponds to @cpp{void
(*)(void*, void*)}, but using the stdcall calling convention
on Windows.}
@item{@racket['osapi_ptr_int_int_int_int_ptr_int_int_long->void]
corresponds to @cpp{void (*)(void*, int, int, int, int, void*,
int, int, long)}, but using the stdcall calling convention
on Windows.}
]
After the protocol symbol, the vector should contain a pointer to a
foreign function and then an element for each of the function's
arguments. Pointer values are represented as for the @racket[_pointer]
representation defined by @racketmodname[ffi/unsafe].
The result is a key for use with @cpp{scheme_remove_gc_callback}. If
the key becomes inaccessible, then the callback will be removed
automatically (but beware that the pre-callback will have executed and
the post-callback will not have executed)
}
@defproc[(unsafe-remove-collect-callbacks [key any/c]) void?]{
Unregisters pre- and post-collection callbacks that were previously
registered by a call to @racket[unsafe-add-collect-callbacks] that
returned @racket[v].}

View File

@ -18,6 +18,7 @@
@include-section["port.scrbl"]
@include-section["global.scrbl"]
@include-section["os-thread.scrbl"]
@include-section["collect-callback.scrbl"]
@include-section["objc.scrbl"]
@include-section["ns.scrbl"]
@include-section["com.scrbl"]

View File

@ -1,5 +1,6 @@
#lang scribble/doc
@(require "utils.rkt" (for-label ffi/unsafe))
@(require "utils.rkt" (for-label ffi/unsafe
ffi/unsafe/collect-callback))
@title[#:tag "im:memoryalloc"]{Memory Allocation}
@ -1176,60 +1177,11 @@ array @var{shape}, so the array need not be retained.
@function[(Scheme_Object* scheme_add_gc_callback [Scheme_Object* pre_desc]
[Scheme_Object* post_desc])]{
Registers descriptions of foreign functions to be called just before
and just after a garbage collection. The foreign functions must not
allocate garbage-collected memory, and they are called in a way that
does not allocate, which is why @var{pre_desc} and @var{post_desc} are
function descriptions instead of thunks.
A description is a vector of vectors, where each of the inner vectors
describes a single call, and the calls are performed in sequence. Each
call vector starts with a symbol that indicates the protocol of the
foreign function to be called. The following protocols are supported:
@itemlist[
@item{@racket['ptr_ptr_ptr->void] corresponds to @cpp{void
(*)(void*, void*, void*)}.}
@item{@racket['ptr_ptr_ptr_int->void] corresponds to @cpp{void
(*)(void*, void*, void*, int)}.}
@item{@racket['ptr_ptr_float->void] corresponds to @cpp{void
(*)(void*, void*, float)}.}
@item{@racket['ptr_ptr_double->void] corresponds to @cpp{void
(*)(void*, void*, double)}.}
@item{@racket['ptr_ptr_ptr_int_int_int_int_int_int_int_int_int->void]
corresponds to @cpp{void (*)(void*, void*, void*, int, int, int, int,
int, int, int, int, int)}.}
@item{@racket['osapi_ptr_int->void] corresponds to @cpp{void
(*)(void*, int)}, but using the stdcall calling convention
on Windows.}
@item{@racket['osapi_ptr_ptr->void] corresponds to @cpp{void
(*)(void*, void*)}, but using the stdcall calling convention
on Windows.}
@item{@racket['osapi_ptr_int_int_int_int_ptr_int_int_long->void]
corresponds to @cpp{void (*)(void*, int, int, int, int, void*,
int, int, long)}, but using the stdcall calling convention
on Windows.}
]
After the protocol symbol, the vector should contain a pointer to a
foreign function and then an element for each of the function's
arguments. Pointer values are represented as for the @racket[_pointer]
representation defined by @racketmodname[ffi/unsafe].
The result is a key for use with @cpp{scheme_remove_gc_callback}. If
the key becomes inaccessible, then the callback will be removed
automatically (but beware that the pre-callback will have executed and
the post-callback will not have executed).}
The same as @racket[unsafe-add-collect-callbacks] from
@racketmodname[ffi/unsafe/collect-callback].}
@function[(void scheme_remove_gc_callback [Scheme_Object* key])]{
Removes a garbage-collection callback installed with @cpp{scheme_add_gc_callback}.}
The same as @racket[unsafe-remove-collect-callbacks], removes
garbage-collection callbacks installed with
@cpp{scheme_add_gc_callback}.}

View File

@ -0,0 +1,5 @@
#lang racket/base
(require '#%unsafe)
(provide unsafe-add-collect-callbacks
unsafe-remove-collect-callbacks)

View File

@ -41,7 +41,9 @@
unsafe-call-in-os-thread
unsafe-make-os-semaphore
unsafe-os-semaphore-post
unsafe-os-semaphore-wait)
unsafe-os-semaphore-wait
unsafe-add-collect-callbacks
unsafe-remove-collect-callbacks)
(rename-out [new:unsafe-impersonate-procedure unsafe-impersonate-procedure]
[new:unsafe-chaperone-procedure unsafe-chaperone-procedure])
(prefix-out unsafe-

View File

@ -6,6 +6,7 @@
[prop:chaperone-unsafe-undefined (known-constant)]
[unsafe-abort-current-continuation/no-wind (known-procedure 4)]
[unsafe-add-post-custodian-shutdown (known-procedure 2)]
[unsafe-add-collect-callbacks (known-procedure 4)]
[unsafe-box*-cas! (known-procedure 8)]
[unsafe-bytes-length (known-procedure/succeeds 2)]
[unsafe-bytes-ref (known-procedure 4)]
@ -123,6 +124,7 @@
[unsafe-port->file-descriptor (known-procedure 2)]
[unsafe-port->socket (known-procedure 2)]
[unsafe-register-process-global (known-procedure 4)]
[unsafe-remove-collect-callbacks (known-procedure 2)]
[unsafe-s16vector-ref (known-procedure 4)]
[unsafe-s16vector-set! (known-procedure 8)]
[unsafe-set-box! (known-procedure 4)]

View File

@ -453,6 +453,8 @@
make-phantom-bytes
set-phantom-bytes!
set-garbage-collect-notify! ; not exported to Racket
unsafe-add-collect-callbacks
unsafe-remove-collect-callbacks
;; not the same as Racket will executors:
(rename

View File

@ -52,6 +52,7 @@
[(zero? (bitwise-and c collect-generation-radix-mask))
(loop (bitwise-arithmetic-shift-right c log-collect-generation-radix) (add1 gen))]
[else gen]))])])
(run-collect-callbacks car)
(collect gen)
(let ([post-allocated (bytes-allocated)])
(when (= gen (collect-maximum-generation))
@ -59,7 +60,8 @@
(garbage-collect-notify gen
pre-allocated pre-allocated+overhead pre-time pre-cpu-time
post-allocated (current-memory-bytes) (real-time) (cpu-time)))
(poll-foreign-guardian))))
(poll-foreign-guardian)
(run-collect-callbacks cdr))))
(define collect-garbage
(case-lambda
@ -295,3 +297,90 @@
(check who phantom-bytes? phantom-bstr)
(check who exact-nonnegative-integer? k)
(phantom-bytes-size-set! phantom-bstr k))
;; ----------------------------------------
;; List of (cons <pre> <post>)
(define collect-callbacks '())
(define (unsafe-add-collect-callbacks pre post)
(let ([p (cons pre post)])
(with-interrupts-disabled
(set! collect-callbacks (cons p collect-callbacks)))
p))
(define (unsafe-remove-collect-callbacks p)
(with-interrupts-disabled
(set! collect-callbacks (#%remq p collect-callbacks))))
(define (run-collect-callbacks sel)
(let loop ([l collect-callbacks])
(unless (null? l)
(let ([v (sel (car l))])
(let loop ([i 0] [save #f])
(unless (fx= i (#%vector-length v))
(loop (fx+ i 1)
(run-one-collect-callback (#%vector-ref v i) save sel))))
(loop (cdr l))))))
(define-syntax (osapi-foreign-procedure stx)
(syntax-case stx ()
[(_ s ...)
(case (machine-type)
[(a6nt ta6nt i3nt ti3nt) #'(foreign-procedure _stdcall s ...)]
[else #'(foreign-procedure s ...)])]))
;; This is an inconvenient callback interface, certainly, but it
;; accomodates a limitatuon of the traditional Racket implementation
(define (run-one-collect-callback v save sel)
(let ([protocol (#%vector-ref v 0)]
[proc (cpointer-address (#%vector-ref v 1))]
[ptr (lambda (i)
(cpointer*-address (#%vector-ref v (fx+ 2 i))))]
[val (lambda (i)
(#%vector-ref v (fx+ 2 i)))])
(case protocol
[(int->void)
((foreign-procedure proc (int) void) (val 0))
save]
[(ptr_ptr_ptr_int->void)
((foreign-procedure proc (void* void* void* int) void) (ptr 0) (ptr 1) (ptr 2) (val 3))
save]
[(ptr_ptr->save)
((foreign-procedure proc (void* void*) void*) (ptr 0) (ptr 1))]
[(save!_ptr->void)
(and save (not (eqv? save 0))
((foreign-procedure proc (void* void*) void*) save (ptr 0))
save)]
[(ptr_ptr_ptr->void)
((foreign-procedure proc (void* void* void*) void) (ptr 0) (ptr 1) (ptr 2))
save]
[(ptr_ptr_float->void)
((foreign-procedure proc (void* void* float) void) (ptr 0) (ptr 1) (val 2))
save]
[(ptr_ptr_double->void)
((foreign-procedure proc (void* void* double) void) (ptr 0) (ptr 1) (val 2))
save]
[(float_float_float_float->void)
((foreign-procedure proc (float float float float) void) (val 0) (val 1) (val 2) (val 3))
save]
[(ptr_ptr_ptr_int_int_int_int_int_int_int_int_int->void)
((foreign-procedure proc (void* void* void* int int int int int int int int int) void)
(ptr 0) (ptr 2) (ptr 2)
(val 3) (val 4) (val 5) (val 6)
(val 7) (val 8) (val 9) (val 10) (val 11))
save]
[(osapi_ptr_ptr->void)
((osapi-foreign-procedure proc (void* void*) void) (ptr 0) (ptr 1))
save]
[(osapi_ptr_int->void)
((osapi-foreign-procedure proc (void* int) void) (ptr 0) (val 1))
save]
[(osapi_ptr_int_int_int_int_ptr_int_int_long->void)
((osapi-foreign-procedure proc (void* int int int int void* int int long) void)
(ptr 0) (val 1) (val 2) (val 3) (val 4)
(ptr 5) (val 6) (val 7) (val 8))
save]
[else
(eprintf "unrecognized collect-callback protocol: ~s\n" protocol)
save])))

View File

@ -14,7 +14,7 @@
#define USE_COMPILED_STARTUP 1
#define EXPECTED_PRIM_COUNT 1435
#define EXPECTED_PRIM_COUNT 1437
#ifdef MZSCHEME_SOMETHING_OMITTED
# undef USE_COMPILED_STARTUP

View File

@ -13,12 +13,12 @@
consistently.)
*/
#define MZSCHEME_VERSION "7.0.0.8"
#define MZSCHEME_VERSION "7.0.0.9"
#define MZSCHEME_VERSION_X 7
#define MZSCHEME_VERSION_Y 0
#define MZSCHEME_VERSION_Z 0
#define MZSCHEME_VERSION_W 8
#define MZSCHEME_VERSION_W 9
#define MZSCHEME_VERSION_MAJOR ((MZSCHEME_VERSION_X * 100) + MZSCHEME_VERSION_Y)
#define MZSCHEME_VERSION_MINOR ((MZSCHEME_VERSION_Z * 1000) + MZSCHEME_VERSION_W)

View File

@ -347,6 +347,9 @@ static Scheme_Object *unsafe_make_os_semaphore(int argc, Scheme_Object *argv[]);
static Scheme_Object *unsafe_os_semaphore_wait(int argc, Scheme_Object *argv[]);
static Scheme_Object *unsafe_os_semaphore_post(int argc, Scheme_Object *argv[]);
static Scheme_Object *unsafe_add_collect_callbacks(int argc, Scheme_Object *argv[]);
static Scheme_Object *unsafe_remove_collect_callbacks(int argc, Scheme_Object *argv[]);
static Scheme_Object *make_plumber(int argc, Scheme_Object *argv[]);
static Scheme_Object *plumber_p(int argc, Scheme_Object *argv[]);
static Scheme_Object *plumber_flush_all(int argc, Scheme_Object *argv[]);
@ -675,6 +678,9 @@ scheme_init_unsafe_thread (Scheme_Startup_Env *env)
ADD_PRIM_W_ARITY("unsafe-make-os-semaphore", unsafe_make_os_semaphore, 0, 0, env);
ADD_PRIM_W_ARITY("unsafe-os-semaphore-wait", unsafe_os_semaphore_wait, 1, 1, env);
ADD_PRIM_W_ARITY("unsafe-os-semaphore-post", unsafe_os_semaphore_post, 1, 1, env);
ADD_PRIM_W_ARITY("unsafe-add-collect-callbacks", unsafe_add_collect_callbacks, 2, 2, env);
ADD_PRIM_W_ARITY("unsafe-remove-collect-callbacks", unsafe_remove_collect_callbacks, 1, 1, env);
}
void scheme_init_thread_places(void) {
@ -8747,6 +8753,18 @@ void scheme_remove_gc_callback(Scheme_Object *key)
}
}
static Scheme_Object *unsafe_add_collect_callbacks(int argc, Scheme_Object *argv[])
{
return scheme_add_gc_callback(argv[0], argv[1]);
}
static Scheme_Object *unsafe_remove_collect_callbacks(int argc, Scheme_Object *argv[])
{
scheme_remove_gc_callback(argv[0]);
return scheme_void;
}
#if defined(_MSC_VER) || defined(__MINGW32__)
# define mzOSAPI WINAPI
#else