aboutsummaryrefslogtreecommitdiffstats
path: root/src/comp.c
diff options
context:
space:
mode:
authorNicolás Bértolo2020-05-19 15:57:31 -0300
committerAndrea Corallo2020-05-25 09:42:10 +0100
commit1b809f378f6263bc099da45c5e4a42c89fef8d71 (patch)
tree1adfe55b6b0761de4f12bc0830aaab7a3296bc07 /src/comp.c
parent9daffe9cfe82d3b1e1e9fa8929dbb40cfed60f0f (diff)
downloademacs-1b809f378f6263bc099da45c5e4a42c89fef8d71.tar.gz
emacs-1b809f378f6263bc099da45c5e4a42c89fef8d71.zip
Improve handling of native compilation units still in use in Windows
When closing emacs will inspect all directories from which it loaded native compilation units. If it finds a ".eln.old" file it will try to delete it, if it fails that means that another Emacs instance is using it. When compiling a file we rename the file that was in the output path in case it has been loaded into another Emacs instance. When deleting a package we move any ".eln" or ".eln.old" files in the package folder that we can't delete to `package-user-dir`. Emacs will check that directory when closing and delete them. * lisp/emacs-lisp/comp.el (comp--replace-output-file): Function called from C code to finish the compilation process. It performs renaming of the old file if necessary. * lisp/emacs-lisp/package.el (package--delete-directory): Function to delete a package directory. It moves native compilation units that it can't delete to `package-user-dir'. * src/alloc.c (cleanup_vector): Call dispose_comp_unit(). (garbage_collect): Call finish_delayed_disposal_of_comp_units(). * src/comp.c: Restore the signal mask using unwind-protect. Store loaded native compilation units in a hash table for disposal on close. Store filenames of native compilation units GC'd in a linked list to finish their disposal when the GC is over. (clean_comp_unit_directory): Delete all *.eln.old files in a directory. (clean_package_user_dir_of_old_comp_units): Delete all *.eln.old files in `package-user-dir'. (dispose_all_remaining_comp_units): Dispose of native compilation units that are still loaded. (dispose_comp_unit): Close handle and cleanup directory or arrange for later cleanup if DELAY is true. (finish_delayed_disposal_of_comp_units): Dispose of native compilation units that were GC'd. (register_native_comp_unit): Register native compilation unit for disposal when Emacs closes. * src/comp.h: Introduce cfile member in Lisp_Native_Comp_Unit. Add declarations of functions that: clean directories of unused native compilation units, handle disposal of native compilation units. * src/emacs.c (kill-emacs): Dispose all remaining compilation units right right before calling exit(). * src/eval.c (internal_condition_case_3, internal_condition_case_4): Add functions. * src/lisp.h (internal_condition_case_3, internal_condition_case_4): Add functions. * src/pdumper.c (dump_do_dump_relocation): Set cfile to a copy of the Lisp string specifying the file path.
Diffstat (limited to 'src/comp.c')
-rw-r--r--src/comp.c260
1 files changed, 250 insertions, 10 deletions
diff --git a/src/comp.c b/src/comp.c
index 68ad6d3eb8d..16ad77c74bc 100644
--- a/src/comp.c
+++ b/src/comp.c
@@ -411,6 +411,10 @@ load_gccjit_if_necessary (bool mandatory)
411#define CALL1I(fun, arg) \ 411#define CALL1I(fun, arg) \
412 CALLN (Ffuncall, intern_c_string (STR (fun)), arg) 412 CALLN (Ffuncall, intern_c_string (STR (fun)), arg)
413 413
414/* Like call2 but stringify and intern. */
415#define CALL2I(fun, arg1, arg2) \
416 CALLN (Ffuncall, intern_c_string (STR (fun)), arg1, arg2)
417
414#define DECL_BLOCK(name, func) \ 418#define DECL_BLOCK(name, func) \
415 gcc_jit_block *(name) = \ 419 gcc_jit_block *(name) = \
416 gcc_jit_function_new_block ((func), STR (name)) 420 gcc_jit_function_new_block ((func), STR (name))
@@ -435,6 +439,8 @@ typedef struct {
435 ptrdiff_t size; 439 ptrdiff_t size;
436} f_reloc_t; 440} f_reloc_t;
437 441
442sigset_t saved_sigset;
443
438static f_reloc_t freloc; 444static f_reloc_t freloc;
439 445
440/* C side of the compiler context. */ 446/* C side of the compiler context. */
@@ -3795,6 +3801,13 @@ DEFUN ("comp--release-ctxt", Fcomp__release_ctxt, Scomp__release_ctxt,
3795 return Qt; 3801 return Qt;
3796} 3802}
3797 3803
3804static void
3805restore_sigmask (void)
3806{
3807 pthread_sigmask (SIG_SETMASK, &saved_sigset, 0);
3808 unblock_input ();
3809}
3810
3798DEFUN ("comp--compile-ctxt-to-file", Fcomp__compile_ctxt_to_file, 3811DEFUN ("comp--compile-ctxt-to-file", Fcomp__compile_ctxt_to_file,
3799 Scomp__compile_ctxt_to_file, 3812 Scomp__compile_ctxt_to_file,
3800 1, 1, 0, 3813 1, 1, 0,
@@ -3816,6 +3829,8 @@ DEFUN ("comp--compile-ctxt-to-file", Fcomp__compile_ctxt_to_file,
3816 CALL1I (comp-data-container-idx, CALL1I (comp-ctxt-d-ephemeral, Vcomp_ctxt)); 3829 CALL1I (comp-data-container-idx, CALL1I (comp-ctxt-d-ephemeral, Vcomp_ctxt));
3817 3830
3818 sigset_t oldset; 3831 sigset_t oldset;
3832 ptrdiff_t count = 0;
3833
3819 if (!noninteractive) 3834 if (!noninteractive)
3820 { 3835 {
3821 sigset_t blocked; 3836 sigset_t blocked;
@@ -3828,6 +3843,8 @@ DEFUN ("comp--compile-ctxt-to-file", Fcomp__compile_ctxt_to_file,
3828 sigaddset (&blocked, SIGIO); 3843 sigaddset (&blocked, SIGIO);
3829#endif 3844#endif
3830 pthread_sigmask (SIG_BLOCK, &blocked, &oldset); 3845 pthread_sigmask (SIG_BLOCK, &blocked, &oldset);
3846 count = SPECPDL_INDEX ();
3847 record_unwind_protect_void (restore_sigmask);
3831 } 3848 }
3832 emit_ctxt_code (); 3849 emit_ctxt_code ();
3833 3850
@@ -3866,18 +3883,10 @@ DEFUN ("comp--compile-ctxt-to-file", Fcomp__compile_ctxt_to_file,
3866 GCC_JIT_OUTPUT_KIND_DYNAMIC_LIBRARY, 3883 GCC_JIT_OUTPUT_KIND_DYNAMIC_LIBRARY,
3867 SSDATA (tmp_file)); 3884 SSDATA (tmp_file));
3868 3885
3869 /* Remove the old eln instead of copying the new one into it to get 3886 CALL2I(comp--replace-output-file, out_file, tmp_file);
3870 a new inode and prevent crashes in case the old one is currently
3871 loaded. */
3872 if (!NILP (Ffile_exists_p (out_file)))
3873 Fdelete_file (out_file, Qnil);
3874 Frename_file (tmp_file, out_file, Qnil);
3875 3887
3876 if (!noninteractive) 3888 if (!noninteractive)
3877 { 3889 unbind_to (count, Qnil);
3878 pthread_sigmask (SIG_SETMASK, &oldset, 0);
3879 unblock_input ();
3880 }
3881 3890
3882 return out_file; 3891 return out_file;
3883} 3892}
@@ -3939,6 +3948,223 @@ helper_PSEUDOVECTOR_TYPEP_XUNTAG (Lisp_Object a, enum pvec_type code)
3939} 3948}
3940 3949
3941 3950
3951/*********************************/
3952/* Disposal of compilation units */
3953/*********************************/
3954
3955/*
3956The problem: Windows does not let us delete an .eln file that has been
3957loaded by a process. This has two implications in Emacs:
3958
39591) It is not possible to recompile a lisp file if the corresponding
3960.eln file has been loaded. This is because we'd like to use the same
3961filename, but we can't delete the old .eln file.
3962
39632) It is not possible to delete a package using `package-delete'
3964if an .eln file has been loaded.
3965
3966* General idea
3967
3968The solution to these two problems is to move the foo.eln file
3969somewhere else and have the last Emacs instance using it delete it.
3970To make it easy to find what files need to be removed we use two approaches.
3971
3972In the 1) case we rename foo.eln to fooXXXXXX.eln.old in the same
3973folder. When Emacs is unloading "foo" (either GC'd the native
3974compilation unit or Emacs is closing (see below)) we delete all the
3975.eln.old files in the folder where the original foo.eln was stored.
3976
3977Ideally we'd figure out the new name of foo.eln and delete it if
3978it ends in .eln.old. There is no simple API to do this in
3979Windows. GetModuleFileName() returns the original filename, not the
3980current one. This forces us to put .eln.old files in an agreed upon
3981path. We cannot use %TEMP% because it may be in another drive and then
3982the rename operation would fail.
3983
3984In the 2) case we can't use the same folder where the .eln file
3985resided, as we are trying to completely remove the package. Since we
3986are removing packages we can safely move the .eln.old file to
3987`package-user-dir' as we are sure that that would not mean changing
3988drives.
3989
3990* Implementation details
3991
3992The concept of disposal of a native compilation unit refers to
3993unloading the shared library and deleting all the .eln.old files in
3994the directory. These are two separate steps. We'll call them
3995early-disposal and late-disposal.
3996
3997There are two data structures used:
3998
3999- The `all_loaded_comp_units_h` hashtable.
4000
4001This hashtable is used like an array of weak references to native
4002compilation units. This hash table is filled by load_comp_unit() and
4003dispose_all_remaining_comp_units() iterates over all values that were
4004not disposed by the GC and performs all disposal steps when Emacs is
4005closing.
4006
4007- The `delayed_comp_unit_disposal_list` list.
4008
4009This is were the dispose_comp_unit() function, when called by the GC
4010sweep stage, stores the original filenames of the disposed native
4011compilation units. This is an ad-hoc C structure instead of a Lisp
4012cons because we need to allocate instances of this structure during
4013the GC.
4014
4015The finish_delayed_disposal_of_comp_units() function will iterate over
4016this list and perform the late-disposal step when Emacs is closing.
4017
4018*/
4019
4020#ifdef WINDOWSNT
4021#define OLD_ELN_SUFFIX_REGEXP build_string ("\\.eln\\.old\\'")
4022
4023static Lisp_Object all_loaded_comp_units_h;
4024
4025/* We need to allocate instances of this struct during a GC
4026 * sweep. This is why it can't be transformed into a simple cons.
4027 */
4028struct delayed_comp_unit_disposal
4029{
4030 struct delayed_comp_unit_disposal *next;
4031 char *filename;
4032};
4033
4034struct delayed_comp_unit_disposal *delayed_comp_unit_disposal_list;
4035
4036static Lisp_Object
4037return_nil (Lisp_Object arg)
4038{
4039 return Qnil;
4040}
4041
4042/* Tries to remove all *.eln.old files in DIRNAME.
4043
4044 * Any error is ignored because it may be due to the file being loaded
4045 * in another Emacs instance.
4046 */
4047static void
4048clean_comp_unit_directory (Lisp_Object dirpath)
4049{
4050 if (NILP (dirpath))
4051 return;
4052 Lisp_Object files_in_dir;
4053 files_in_dir = internal_condition_case_4 (Fdirectory_files, dirpath, Qt,
4054 OLD_ELN_SUFFIX_REGEXP, Qnil, Qt,
4055 return_nil);
4056 FOR_EACH_TAIL (files_in_dir) { DeleteFile (SSDATA (XCAR (files_in_dir))); }
4057}
4058
4059/* Tries to remove all *.eln.old files in `package-user-dir'.
4060
4061 * This is called when Emacs is closing to clean any *.eln left from a
4062 * deleted package.
4063 */
4064void
4065clean_package_user_dir_of_old_comp_units (void)
4066{
4067 Lisp_Object package_user_dir
4068 = find_symbol_value (intern ("package-user-dir"));
4069 if (EQ (package_user_dir, Qunbound) || !STRINGP (package_user_dir))
4070 return;
4071
4072 clean_comp_unit_directory (package_user_dir);
4073}
4074
4075/* This function disposes all compilation units that are still loaded.
4076 * It is important that this function is called only right before
4077 * Emacs is closed, otherwise we risk running a subr that is
4078 * implemented in an unloaded dynamic library.
4079 */
4080void
4081dispose_all_remaining_comp_units (void)
4082{
4083 struct Lisp_Hash_Table *h = XHASH_TABLE (all_loaded_comp_units_h);
4084
4085 for (ptrdiff_t i = 0; i < HASH_TABLE_SIZE (h); ++i)
4086 {
4087 Lisp_Object k = HASH_KEY (h, i);
4088 if (!EQ (k, Qunbound))
4089 {
4090 Lisp_Object val = HASH_VALUE (h, i);
4091 struct Lisp_Native_Comp_Unit *cu = XNATIVE_COMP_UNIT (val);
4092 dispose_comp_unit (cu, false);
4093 }
4094 }
4095}
4096
4097/* This function finishes the disposal of compilation units that were
4098 * passed to `dispose_comp_unit` with DELAY == true.
4099 *
4100 * This function is called when Emacs is idle and when it is about to
4101 * close.
4102 */
4103void
4104finish_delayed_disposal_of_comp_units (void)
4105{
4106 for (struct delayed_comp_unit_disposal *item
4107 = delayed_comp_unit_disposal_list;
4108 delayed_comp_unit_disposal_list; item = delayed_comp_unit_disposal_list)
4109 {
4110 delayed_comp_unit_disposal_list = item->next;
4111 Lisp_Object dirname = internal_condition_case_1 (
4112 Ffile_name_directory, build_string (item->filename), Qt, return_nil);
4113 clean_comp_unit_directory (dirname);
4114 xfree (item->filename);
4115 xfree (item);
4116 }
4117}
4118#endif
4119
4120/* This function puts the compilation unit in the
4121 * `all_loaded_comp_units_h` hashmap.
4122 */
4123static void
4124register_native_comp_unit (Lisp_Object comp_u)
4125{
4126#ifdef WINDOWSNT
4127 Fputhash (CALL1I (gensym, Qnil), comp_u, all_loaded_comp_units_h);
4128#endif
4129}
4130
4131/* This function disposes compilation units. It is called during the GC sweep
4132 * stage and when Emacs is closing.
4133
4134 * On Windows the the DELAY parameter specifies whether the native
4135 * compilation file will be deleted right away (if necessary) or put
4136 * on a list. That list will be dealt with by
4137 * `finish_delayed_disposal_of_comp_units`.
4138 */
4139void
4140dispose_comp_unit (struct Lisp_Native_Comp_Unit *comp_handle, bool delay)
4141{
4142 eassert (comp_handle->handle);
4143 dynlib_close (comp_handle->handle);
4144#ifdef WINDOWSNT
4145 if (!delay)
4146 {
4147 Lisp_Object dirname = internal_condition_case_1 (
4148 Ffile_name_directory, build_string (comp_handle->cfile), Qt,
4149 return_nil);
4150 if (!NILP (dirname))
4151 clean_comp_unit_directory (dirname);
4152 xfree (comp_handle->cfile);
4153 comp_handle->cfile = NULL;
4154 }
4155 else
4156 {
4157 struct delayed_comp_unit_disposal *head;
4158 head = xmalloc (sizeof (struct delayed_comp_unit_disposal));
4159 head->next = delayed_comp_unit_disposal_list;
4160 head->filename = comp_handle->cfile;
4161 comp_handle->cfile = NULL;
4162 delayed_comp_unit_disposal_list = head;
4163 }
4164#endif
4165}
4166
4167
3942/***********************************/ 4168/***********************************/
3943/* Deferred compilation mechanism. */ 4169/* Deferred compilation mechanism. */
3944/***********************************/ 4170/***********************************/
@@ -4159,6 +4385,12 @@ load_comp_unit (struct Lisp_Native_Comp_Unit *comp_u, bool loading_dump,
4159 d_vec_len = XFIXNUM (Flength (comp_u->data_impure_vec)); 4385 d_vec_len = XFIXNUM (Flength (comp_u->data_impure_vec));
4160 for (EMACS_INT i = 0; i < d_vec_len; i++) 4386 for (EMACS_INT i = 0; i < d_vec_len; i++)
4161 data_imp_relocs[i] = AREF (comp_u->data_impure_vec, i); 4387 data_imp_relocs[i] = AREF (comp_u->data_impure_vec, i);
4388
4389 /* If we register them while dumping we will get some entries in
4390 the hash table that will be duplicated when pdumper calls
4391 load_comp_unit. */
4392 if (!will_dump_p ())
4393 register_native_comp_unit (comp_u_lisp_obj);
4162 } 4394 }
4163 4395
4164 if (!loading_dump) 4396 if (!loading_dump)
@@ -4316,6 +4548,9 @@ DEFUN ("native-elisp-load", Fnative_elisp_load, Snative_elisp_load, 1, 2, 0,
4316 if (!comp_u->handle) 4548 if (!comp_u->handle)
4317 xsignal2 (Qnative_lisp_load_failed, file, build_string (dynlib_error ())); 4549 xsignal2 (Qnative_lisp_load_failed, file, build_string (dynlib_error ()));
4318 comp_u->file = file; 4550 comp_u->file = file;
4551#ifdef WINDOWSNT
4552 comp_u->cfile = xlispstrdup (file);
4553#endif
4319 comp_u->data_vec = Qnil; 4554 comp_u->data_vec = Qnil;
4320 comp_u->lambda_gc_guard = CALLN (Fmake_hash_table, QCtest, Qeq); 4555 comp_u->lambda_gc_guard = CALLN (Fmake_hash_table, QCtest, Qeq);
4321 comp_u->lambda_c_name_idx_h = CALLN (Fmake_hash_table, QCtest, Qequal); 4556 comp_u->lambda_c_name_idx_h = CALLN (Fmake_hash_table, QCtest, Qequal);
@@ -4464,6 +4699,11 @@ syms_of_comp (void)
4464 staticpro (&delayed_sources); 4699 staticpro (&delayed_sources);
4465 delayed_sources = Qnil; 4700 delayed_sources = Qnil;
4466 4701
4702#ifdef WINDOWSNT
4703 staticpro (&all_loaded_comp_units_h);
4704 all_loaded_comp_units_h = CALLN(Fmake_hash_table, QCweakness, Qvalue);
4705#endif
4706
4467 DEFVAR_LISP ("comp-ctxt", Vcomp_ctxt, 4707 DEFVAR_LISP ("comp-ctxt", Vcomp_ctxt,
4468 doc: /* The compiler context. */); 4708 doc: /* The compiler context. */);
4469 Vcomp_ctxt = Qnil; 4709 Vcomp_ctxt = Qnil;