diff options
| author | Philipp Stephani | 2018-02-11 21:38:22 +0100 |
|---|---|---|
| committer | Philipp Stephani | 2020-01-03 19:24:10 +0100 |
| commit | 48ffef5ef4b34799941a033591ea827d40025939 (patch) | |
| tree | 67b00c1bc546f3c9ef601c10db634da3094f7f57 | |
| parent | 2b6d702e5d2d572640c6bcd43f54138bacbe7ac8 (diff) | |
| download | emacs-48ffef5ef4b34799941a033591ea827d40025939.tar.gz emacs-48ffef5ef4b34799941a033591ea827d40025939.zip | |
Implement finalizers for module functions (Bug#30373)
* src/module-env-28.h: Add new module environment functions to
module environment for Emacs 28.
* src/emacs-module.h.in: Document that 'emacs_finalizer' also works
for function finalizers.
* src/emacs-module.c (CHECK_MODULE_FUNCTION): New function.
(struct Lisp_Module_Function): Add finalizer data member.
(module_make_function): Initialize finalizer.
(module_get_function_finalizer)
(module_set_function_finalizer): New module environment functions.
(module_finalize_function): New function.
(initialize_environment): Initialize new environment functions.
* src/alloc.c (cleanup_vector): Call potential module function
finalizer during garbage collection.
* test/data/emacs-module/mod-test.c (signal_error): New helper
function.
(memory_full): Use it.
(finalizer): New example function finalizer.
(Fmod_test_make_function_with_finalizer)
(Fmod_test_function_finalizer_calls): New test module functions.
(emacs_module_init): Define them.
* test/src/emacs-module-tests.el (module/function-finalizer): New unit
test.
* doc/lispref/internals.texi (Module Functions): Document new
functionality.
(Module Misc): Move description of 'emacs_finalizer' type to 'Module
Functions' node, and add a reference to it.
* etc/NEWS: Mention new functionality.
| -rw-r--r-- | doc/lispref/internals.texi | 55 | ||||
| -rw-r--r-- | etc/NEWS | 5 | ||||
| -rw-r--r-- | src/alloc.c | 6 | ||||
| -rw-r--r-- | src/emacs-module.c | 36 | ||||
| -rw-r--r-- | src/emacs-module.h.in | 4 | ||||
| -rw-r--r-- | src/lisp.h | 1 | ||||
| -rw-r--r-- | src/module-env-28.h | 8 | ||||
| -rw-r--r-- | test/data/emacs-module/mod-test.c | 49 | ||||
| -rw-r--r-- | test/src/emacs-module-tests.el | 8 |
9 files changed, 163 insertions, 9 deletions
diff --git a/doc/lispref/internals.texi b/doc/lispref/internals.texi index d95a3e445cc..c0b3fe5a1b0 100644 --- a/doc/lispref/internals.texi +++ b/doc/lispref/internals.texi | |||
| @@ -1447,6 +1447,54 @@ The Lisp package which goes with your module could then load the | |||
| 1447 | module using the @code{load} primitive (@pxref{Dynamic Modules}) when | 1447 | module using the @code{load} primitive (@pxref{Dynamic Modules}) when |
| 1448 | the package is loaded into Emacs. | 1448 | the package is loaded into Emacs. |
| 1449 | 1449 | ||
| 1450 | @anchor{Module Function Finalizers} | ||
| 1451 | If you want to run some code when a module function object (i.e., an | ||
| 1452 | object returned by @code{make_function}) is garbage-collected, you can | ||
| 1453 | install a @dfn{function finalizer}. Function finalizers are available | ||
| 1454 | since Emacs 28. For example, if you have passed some heap-allocated | ||
| 1455 | structure to the @var{data} argument of @code{make_function}, you can | ||
| 1456 | use the finalizer to deallocate the structure. @xref{Basic | ||
| 1457 | Allocation,,,libc}, and @pxref{Freeing after Malloc,,,libc}. The | ||
| 1458 | finalizer function has the following signature: | ||
| 1459 | |||
| 1460 | @example | ||
| 1461 | void finalizer (void *@var{data}) | ||
| 1462 | @end example | ||
| 1463 | |||
| 1464 | Here, @var{data} receives the value passed to @var{data} when calling | ||
| 1465 | @code{make_function}. Note that the finalizer can't interact with | ||
| 1466 | Emacs in any way. | ||
| 1467 | |||
| 1468 | Directly after calling @code{make_function}, the newly-created | ||
| 1469 | function doesn't have a finalizer. Use @code{set_function_finalizer} | ||
| 1470 | to add one, if desired. | ||
| 1471 | |||
| 1472 | @deftypefun void emacs_finalizer (void *@var{ptr}) | ||
| 1473 | The header @file{emacs-module.h} provides the type | ||
| 1474 | @code{emacs_finalizer} as a type alias for an Emacs finalizer | ||
| 1475 | function. | ||
| 1476 | @end deftypefun | ||
| 1477 | |||
| 1478 | @deftypefun emacs_finalizer get_function_finalizer (emacs_env *@var{env}, emacs_value @var{arg}) | ||
| 1479 | This function, which is available since Emacs 28, returns the function | ||
| 1480 | finalizer associated with the module function represented by | ||
| 1481 | @var{arg}. @var{arg} must refer to a module function, that is, an | ||
| 1482 | object returned by @code{make_function}. If no finalizer is | ||
| 1483 | associated with the function, @code{NULL} is returned. | ||
| 1484 | @end deftypefun | ||
| 1485 | |||
| 1486 | @deftypefun void set_function_finalizer (emacs_env *@var{env}, emacs_value @var{arg}, emacs_finalizer @var{fin}) | ||
| 1487 | This function, which is available since Emacs 28, sets the function | ||
| 1488 | finalizer associated with the module function represented by @var{arg} | ||
| 1489 | to @var{fin}. @var{arg} must refer to a module function, that is, an | ||
| 1490 | object returned by @code{make_function}. @var{fin} can either be | ||
| 1491 | @code{NULL} to clear @var{arg}'s function finalizer, or a pointer to a | ||
| 1492 | function to be called when the object represented by @var{arg} is | ||
| 1493 | garbage-collected. At most one function finalizer can be set per | ||
| 1494 | function; if @var{arg} already has a finalizer, it is replaced by | ||
| 1495 | @var{fin}. | ||
| 1496 | @end deftypefun | ||
| 1497 | |||
| 1450 | @node Module Values | 1498 | @node Module Values |
| 1451 | @subsection Conversion Between Lisp and Module Values | 1499 | @subsection Conversion Between Lisp and Module Values |
| 1452 | @cindex module values, conversion | 1500 | @cindex module values, conversion |
| @@ -1865,11 +1913,8 @@ represented by @var{arg} to be @var{fin}. If @var{fin} is a | |||
| 1865 | finalizer. | 1913 | finalizer. |
| 1866 | @end deftypefn | 1914 | @end deftypefn |
| 1867 | 1915 | ||
| 1868 | @deftypefun void emacs_finalizer (void *@var{ptr}) | 1916 | Note that the @code{emacs_finalizer} type works for both user pointer |
| 1869 | The header @file{emacs-module.h} provides the type | 1917 | an module function finalizers. @xref{Module Function Finalizers}. |
| 1870 | @code{emacs_finalizer} as a type alias for an Emacs finalizer | ||
| 1871 | function. | ||
| 1872 | @end deftypefun | ||
| 1873 | 1918 | ||
| 1874 | @node Module Misc | 1919 | @node Module Misc |
| 1875 | @subsection Miscellaneous Convenience Functions for Modules | 1920 | @subsection Miscellaneous Convenience Functions for Modules |
| @@ -49,6 +49,11 @@ applies, and please also update docstrings as needed. | |||
| 49 | 'emacs_function' and 'emacs_finalizer' for module functions and | 49 | 'emacs_function' and 'emacs_finalizer' for module functions and |
| 50 | finalizers, respectively. | 50 | finalizers, respectively. |
| 51 | 51 | ||
| 52 | ** Module functions can now install an optional finalizer that is | ||
| 53 | called when the function object is garbage-collected. Use | ||
| 54 | 'set_function_finalizer' to set the finalizer and | ||
| 55 | 'get_function_finalizer' to retrieve it. | ||
| 56 | |||
| 52 | 57 | ||
| 53 | * Changes in Emacs 28.1 on Non-Free Operating Systems | 58 | * Changes in Emacs 28.1 on Non-Free Operating Systems |
| 54 | 59 | ||
diff --git a/src/alloc.c b/src/alloc.c index dbe37f44d7c..f59f8cbde9a 100644 --- a/src/alloc.c +++ b/src/alloc.c | |||
| @@ -3027,6 +3027,12 @@ cleanup_vector (struct Lisp_Vector *vector) | |||
| 3027 | if (uptr->finalizer) | 3027 | if (uptr->finalizer) |
| 3028 | uptr->finalizer (uptr->p); | 3028 | uptr->finalizer (uptr->p); |
| 3029 | } | 3029 | } |
| 3030 | else if (PSEUDOVECTOR_TYPEP (&vector->header, PVEC_MODULE_FUNCTION)) | ||
| 3031 | { | ||
| 3032 | ATTRIBUTE_MAY_ALIAS struct Lisp_Module_Function *function | ||
| 3033 | = (struct Lisp_Module_Function *) vector; | ||
| 3034 | module_finalize_function (function); | ||
| 3035 | } | ||
| 3030 | } | 3036 | } |
| 3031 | 3037 | ||
| 3032 | /* Reclaim space used by unmarked vectors. */ | 3038 | /* Reclaim space used by unmarked vectors. */ |
diff --git a/src/emacs-module.c b/src/emacs-module.c index bbb0e3dadd9..3855a33f254 100644 --- a/src/emacs-module.c +++ b/src/emacs-module.c | |||
| @@ -327,6 +327,12 @@ static bool module_assertions = false; | |||
| 327 | MODULE_HANDLE_NONLOCAL_EXIT (error_retval) | 327 | MODULE_HANDLE_NONLOCAL_EXIT (error_retval) |
| 328 | 328 | ||
| 329 | static void | 329 | static void |
| 330 | CHECK_MODULE_FUNCTION (Lisp_Object obj) | ||
| 331 | { | ||
| 332 | CHECK_TYPE (MODULE_FUNCTIONP (obj), Qmodule_function_p, obj); | ||
| 333 | } | ||
| 334 | |||
| 335 | static void | ||
| 330 | CHECK_USER_PTR (Lisp_Object obj) | 336 | CHECK_USER_PTR (Lisp_Object obj) |
| 331 | { | 337 | { |
| 332 | CHECK_TYPE (USER_PTRP (obj), Quser_ptrp, obj); | 338 | CHECK_TYPE (USER_PTRP (obj), Quser_ptrp, obj); |
| @@ -478,6 +484,7 @@ struct Lisp_Module_Function | |||
| 478 | ptrdiff_t min_arity, max_arity; | 484 | ptrdiff_t min_arity, max_arity; |
| 479 | emacs_function subr; | 485 | emacs_function subr; |
| 480 | void *data; | 486 | void *data; |
| 487 | emacs_finalizer finalizer; | ||
| 481 | } GCALIGNED_STRUCT; | 488 | } GCALIGNED_STRUCT; |
| 482 | 489 | ||
| 483 | static struct Lisp_Module_Function * | 490 | static struct Lisp_Module_Function * |
| @@ -511,6 +518,7 @@ module_make_function (emacs_env *env, ptrdiff_t min_arity, ptrdiff_t max_arity, | |||
| 511 | function->max_arity = max_arity; | 518 | function->max_arity = max_arity; |
| 512 | function->subr = func; | 519 | function->subr = func; |
| 513 | function->data = data; | 520 | function->data = data; |
| 521 | function->finalizer = NULL; | ||
| 514 | 522 | ||
| 515 | if (docstring) | 523 | if (docstring) |
| 516 | function->documentation = build_string_from_utf8 (docstring); | 524 | function->documentation = build_string_from_utf8 (docstring); |
| @@ -522,6 +530,32 @@ module_make_function (emacs_env *env, ptrdiff_t min_arity, ptrdiff_t max_arity, | |||
| 522 | return lisp_to_value (env, result); | 530 | return lisp_to_value (env, result); |
| 523 | } | 531 | } |
| 524 | 532 | ||
| 533 | static emacs_finalizer | ||
| 534 | module_get_function_finalizer (emacs_env *env, emacs_value arg) | ||
| 535 | { | ||
| 536 | MODULE_FUNCTION_BEGIN (NULL); | ||
| 537 | Lisp_Object lisp = value_to_lisp (arg); | ||
| 538 | CHECK_MODULE_FUNCTION (lisp); | ||
| 539 | return XMODULE_FUNCTION (lisp)->finalizer; | ||
| 540 | } | ||
| 541 | |||
| 542 | static void | ||
| 543 | module_set_function_finalizer (emacs_env *env, emacs_value arg, | ||
| 544 | emacs_finalizer fin) | ||
| 545 | { | ||
| 546 | MODULE_FUNCTION_BEGIN (); | ||
| 547 | Lisp_Object lisp = value_to_lisp (arg); | ||
| 548 | CHECK_MODULE_FUNCTION (lisp); | ||
| 549 | XMODULE_FUNCTION (lisp)->finalizer = fin; | ||
| 550 | } | ||
| 551 | |||
| 552 | void | ||
| 553 | module_finalize_function (const struct Lisp_Module_Function *func) | ||
| 554 | { | ||
| 555 | if (func->finalizer != NULL) | ||
| 556 | func->finalizer (func->data); | ||
| 557 | } | ||
| 558 | |||
| 525 | static emacs_value | 559 | static emacs_value |
| 526 | module_funcall (emacs_env *env, emacs_value func, ptrdiff_t nargs, | 560 | module_funcall (emacs_env *env, emacs_value func, ptrdiff_t nargs, |
| 527 | emacs_value *args) | 561 | emacs_value *args) |
| @@ -1329,6 +1363,8 @@ initialize_environment (emacs_env *env, struct emacs_env_private *priv) | |||
| 1329 | env->make_time = module_make_time; | 1363 | env->make_time = module_make_time; |
| 1330 | env->extract_big_integer = module_extract_big_integer; | 1364 | env->extract_big_integer = module_extract_big_integer; |
| 1331 | env->make_big_integer = module_make_big_integer; | 1365 | env->make_big_integer = module_make_big_integer; |
| 1366 | env->get_function_finalizer = module_get_function_finalizer; | ||
| 1367 | env->set_function_finalizer = module_set_function_finalizer; | ||
| 1332 | Vmodule_environments = Fcons (make_mint_ptr (env), Vmodule_environments); | 1368 | Vmodule_environments = Fcons (make_mint_ptr (env), Vmodule_environments); |
| 1333 | return env; | 1369 | return env; |
| 1334 | } | 1370 | } |
diff --git a/src/emacs-module.h.in b/src/emacs-module.h.in index 7065f13f2b1..b5ddd7d5fd8 100644 --- a/src/emacs-module.h.in +++ b/src/emacs-module.h.in | |||
| @@ -90,8 +90,8 @@ typedef emacs_value (*emacs_function) (emacs_env *env, ptrdiff_t nargs, | |||
| 90 | void *data) | 90 | void *data) |
| 91 | EMACS_NOEXCEPT EMACS_ATTRIBUTE_NONNULL (1); | 91 | EMACS_NOEXCEPT EMACS_ATTRIBUTE_NONNULL (1); |
| 92 | 92 | ||
| 93 | /* Function prototype for module user-pointer finalizers. These must | 93 | /* Function prototype for module user-pointer and function finalizers. |
| 94 | not throw C++ exceptions. */ | 94 | These must not throw C++ exceptions. */ |
| 95 | typedef void (*emacs_finalizer) (void *data) EMACS_NOEXCEPT; | 95 | typedef void (*emacs_finalizer) (void *data) EMACS_NOEXCEPT; |
| 96 | 96 | ||
| 97 | /* Possible Emacs function call outcomes. */ | 97 | /* Possible Emacs function call outcomes. */ |
diff --git a/src/lisp.h b/src/lisp.h index 356692d53a1..36bb79d67e1 100644 --- a/src/lisp.h +++ b/src/lisp.h | |||
| @@ -4244,6 +4244,7 @@ extern Lisp_Object module_function_documentation | |||
| 4244 | (struct Lisp_Module_Function const *); | 4244 | (struct Lisp_Module_Function const *); |
| 4245 | extern module_funcptr module_function_address | 4245 | extern module_funcptr module_function_address |
| 4246 | (struct Lisp_Module_Function const *); | 4246 | (struct Lisp_Module_Function const *); |
| 4247 | extern void module_finalize_function (const struct Lisp_Module_Function *); | ||
| 4247 | extern void mark_modules (void); | 4248 | extern void mark_modules (void); |
| 4248 | extern void init_module_assertions (bool); | 4249 | extern void init_module_assertions (bool); |
| 4249 | extern void syms_of_module (void); | 4250 | extern void syms_of_module (void); |
diff --git a/src/module-env-28.h b/src/module-env-28.h index dec8704edde..a2479a8f744 100644 --- a/src/module-env-28.h +++ b/src/module-env-28.h | |||
| @@ -1,3 +1,11 @@ | |||
| 1 | /* Add module environment functions newly added in Emacs 28 here. | 1 | /* Add module environment functions newly added in Emacs 28 here. |
| 2 | Before Emacs 28 is released, remove this comment and start | 2 | Before Emacs 28 is released, remove this comment and start |
| 3 | module-env-29.h on the master branch. */ | 3 | module-env-29.h on the master branch. */ |
| 4 | |||
| 5 | void (*(*EMACS_ATTRIBUTE_NONNULL (1) | ||
| 6 | get_function_finalizer) (emacs_env *env, | ||
| 7 | emacs_value arg)) (void *) EMACS_NOEXCEPT; | ||
| 8 | |||
| 9 | void (*set_function_finalizer) (emacs_env *env, emacs_value arg, | ||
| 10 | void (*fin) (void *) EMACS_NOEXCEPT) | ||
| 11 | EMACS_ATTRIBUTE_NONNULL (1); | ||
diff --git a/test/data/emacs-module/mod-test.c b/test/data/emacs-module/mod-test.c index 8dc9ff144af..1a0a879a1bc 100644 --- a/test/data/emacs-module/mod-test.c +++ b/test/data/emacs-module/mod-test.c | |||
| @@ -373,15 +373,20 @@ Fmod_test_add_nanosecond (emacs_env *env, ptrdiff_t nargs, emacs_value *args, | |||
| 373 | } | 373 | } |
| 374 | 374 | ||
| 375 | static void | 375 | static void |
| 376 | memory_full (emacs_env *env) | 376 | signal_error (emacs_env *env, const char *message) |
| 377 | { | 377 | { |
| 378 | const char *message = "Memory exhausted"; | ||
| 379 | emacs_value data = env->make_string (env, message, strlen (message)); | 378 | emacs_value data = env->make_string (env, message, strlen (message)); |
| 380 | env->non_local_exit_signal (env, env->intern (env, "error"), | 379 | env->non_local_exit_signal (env, env->intern (env, "error"), |
| 381 | env->funcall (env, env->intern (env, "list"), 1, | 380 | env->funcall (env, env->intern (env, "list"), 1, |
| 382 | &data)); | 381 | &data)); |
| 383 | } | 382 | } |
| 384 | 383 | ||
| 384 | static void | ||
| 385 | memory_full (emacs_env *env) | ||
| 386 | { | ||
| 387 | signal_error (env, "Memory exhausted"); | ||
| 388 | } | ||
| 389 | |||
| 385 | enum | 390 | enum |
| 386 | { | 391 | { |
| 387 | max_count = ((SIZE_MAX < PTRDIFF_MAX ? SIZE_MAX : PTRDIFF_MAX) | 392 | max_count = ((SIZE_MAX < PTRDIFF_MAX ? SIZE_MAX : PTRDIFF_MAX) |
| @@ -490,6 +495,42 @@ Fmod_test_double (emacs_env *env, ptrdiff_t nargs, emacs_value *args, | |||
| 490 | return result; | 495 | return result; |
| 491 | } | 496 | } |
| 492 | 497 | ||
| 498 | static int function_data; | ||
| 499 | static int finalizer_calls_with_correct_data; | ||
| 500 | static int finalizer_calls_with_incorrect_data; | ||
| 501 | |||
| 502 | static void | ||
| 503 | finalizer (void *data) | ||
| 504 | { | ||
| 505 | if (data == &function_data) | ||
| 506 | ++finalizer_calls_with_correct_data; | ||
| 507 | else | ||
| 508 | ++finalizer_calls_with_incorrect_data; | ||
| 509 | } | ||
| 510 | |||
| 511 | static emacs_value | ||
| 512 | Fmod_test_make_function_with_finalizer (emacs_env *env, ptrdiff_t nargs, | ||
| 513 | emacs_value *args, void *data) | ||
| 514 | { | ||
| 515 | emacs_value fun | ||
| 516 | = env->make_function (env, 2, 2, Fmod_test_sum, NULL, &function_data); | ||
| 517 | env->set_function_finalizer (env, fun, finalizer); | ||
| 518 | if (env->get_function_finalizer (env, fun) != finalizer) | ||
| 519 | signal_error (env, "Invalid finalizer"); | ||
| 520 | return fun; | ||
| 521 | } | ||
| 522 | |||
| 523 | static emacs_value | ||
| 524 | Fmod_test_function_finalizer_calls (emacs_env *env, ptrdiff_t nargs, | ||
| 525 | emacs_value *args, void *data) | ||
| 526 | { | ||
| 527 | emacs_value Flist = env->intern (env, "list"); | ||
| 528 | emacs_value list_args[] | ||
| 529 | = {env->make_integer (env, finalizer_calls_with_correct_data), | ||
| 530 | env->make_integer (env, finalizer_calls_with_incorrect_data)}; | ||
| 531 | return env->funcall (env, Flist, 2, list_args); | ||
| 532 | } | ||
| 533 | |||
| 493 | /* Lisp utilities for easier readability (simple wrappers). */ | 534 | /* Lisp utilities for easier readability (simple wrappers). */ |
| 494 | 535 | ||
| 495 | /* Provide FEATURE to Emacs. */ | 536 | /* Provide FEATURE to Emacs. */ |
| @@ -566,6 +607,10 @@ emacs_module_init (struct emacs_runtime *ert) | |||
| 566 | DEFUN ("mod-test-add-nanosecond", Fmod_test_add_nanosecond, 1, 1, NULL, NULL); | 607 | DEFUN ("mod-test-add-nanosecond", Fmod_test_add_nanosecond, 1, 1, NULL, NULL); |
| 567 | DEFUN ("mod-test-nanoseconds", Fmod_test_nanoseconds, 1, 1, NULL, NULL); | 608 | DEFUN ("mod-test-nanoseconds", Fmod_test_nanoseconds, 1, 1, NULL, NULL); |
| 568 | DEFUN ("mod-test-double", Fmod_test_double, 1, 1, NULL, NULL); | 609 | DEFUN ("mod-test-double", Fmod_test_double, 1, 1, NULL, NULL); |
| 610 | DEFUN ("mod-test-make-function-with-finalizer", | ||
| 611 | Fmod_test_make_function_with_finalizer, 0, 0, NULL, NULL); | ||
| 612 | DEFUN ("mod-test-function-finalizer-calls", | ||
| 613 | Fmod_test_function_finalizer_calls, 0, 0, NULL, NULL); | ||
| 569 | 614 | ||
| 570 | #undef DEFUN | 615 | #undef DEFUN |
| 571 | 616 | ||
diff --git a/test/src/emacs-module-tests.el b/test/src/emacs-module-tests.el index a2cb3e9b498..4f5871be5eb 100644 --- a/test/src/emacs-module-tests.el +++ b/test/src/emacs-module-tests.el | |||
| @@ -402,4 +402,12 @@ See Bug#36226." | |||
| 402 | (load so nil nil :nosuffix :must-suffix) | 402 | (load so nil nil :nosuffix :must-suffix) |
| 403 | (delete-file so)))) | 403 | (delete-file so)))) |
| 404 | 404 | ||
| 405 | (ert-deftest module/function-finalizer () | ||
| 406 | (mod-test-make-function-with-finalizer) | ||
| 407 | (let* ((previous-calls (mod-test-function-finalizer-calls)) | ||
| 408 | (expected-calls (copy-sequence previous-calls))) | ||
| 409 | (cl-incf (car expected-calls)) | ||
| 410 | (garbage-collect) | ||
| 411 | (should (equal (mod-test-function-finalizer-calls) expected-calls)))) | ||
| 412 | |||
| 405 | ;;; emacs-module-tests.el ends here | 413 | ;;; emacs-module-tests.el ends here |