diff options
| author | Ken Raeburn | 2017-05-30 04:45:56 -0400 |
|---|---|---|
| committer | Ken Raeburn | 2017-07-31 01:12:54 -0400 |
| commit | cd0966b33c1fe975520e85e0e7af82c09e4754dc (patch) | |
| tree | 38b5e45900a470123bee96b2be96783022190b76 | |
| parent | f6793d25e8d6c2597d37d9fa65bdcb66cce8fcbe (diff) | |
| download | emacs-cd0966b33c1fe975520e85e0e7af82c09e4754dc.tar.gz emacs-cd0966b33c1fe975520e85e0e7af82c09e4754dc.zip | |
; admin/notes/big-elc: Notes on this experimental branch.
| -rw-r--r-- | admin/notes/big-elc | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/admin/notes/big-elc b/admin/notes/big-elc new file mode 100644 index 00000000000..c63e84da731 --- /dev/null +++ b/admin/notes/big-elc | |||
| @@ -0,0 +1,313 @@ | |||
| 1 | “Big elc file” startup approach -*- mode: org; coding: utf-8 -*- | ||
| 2 | |||
| 3 | These notes discuss the design and implementation status of the “big | ||
| 4 | elc file” approach for saving and loading the Lisp environment. | ||
| 5 | |||
| 6 | * Justification | ||
| 7 | |||
| 8 | The original discussion in which the idea arose was on the possible | ||
| 9 | elimination of the “unexec” mechanism, which is troublesome to | ||
| 10 | maintain. | ||
| 11 | |||
| 12 | The CANNOT_DUMP support, when it isn’t suffering bit-rot, does allow | ||
| 13 | for loading all of the Lisp code from scratch at startup. However, | ||
| 14 | doing so is rather slow. | ||
| 15 | |||
| 16 | Stefan Monnier suggested (and implemented) loading the Lisp | ||
| 17 | environment via loadup.el, as we do now in the “unexec” world, and | ||
| 18 | writing out a single Lisp file with all of the resulting function and | ||
| 19 | variable settings in it. Then a normal Emacs invocation can load this | ||
| 20 | one Lisp file, instead of dozens, and complex data structures can | ||
| 21 | simply be read, instead of constructed at run time. | ||
| 22 | |||
| 23 | It turned out to be desirable for a couple of others to be loaded at | ||
| 24 | run time as well, but the one big file loads most of the settings. | ||
| 25 | |||
| 26 | * Implementation | ||
| 27 | |||
| 28 | ** Saving the Lisp environment | ||
| 29 | |||
| 30 | In loadup.el, we iterate over the obarray, collecting names of faces | ||
| 31 | and coding systems and such for later processing. Each symbol’s | ||
| 32 | function, variable, and property values get turned into the | ||
| 33 | appropriate fset, set-default, or setplist calls. Calls to defvar and | ||
| 34 | make-variable-buffer-local may be generated as well. The resulting | ||
| 35 | forms are all emitted as part of one large “progn” form, so that the | ||
| 36 | print-circle support can correctly cross-link references to objects in | ||
| 37 | a way that the reader will reconstruct. | ||
| 38 | |||
| 39 | A few variables are explicitly skipped because they’re in use during | ||
| 40 | the read process, or they’re intended to be reinitialized when emacs | ||
| 41 | starts up. Some others are skipped for now because they’re not | ||
| 42 | printable objects. | ||
| 43 | |||
| 44 | Most of the support for the unexec path is present, but ignored or | ||
| 45 | commented out. This keeps diffs (and merging) simpler. | ||
| 46 | |||
| 47 | *** charsets, coding systems, and faces | ||
| 48 | |||
| 49 | Some changes to charset and coding system support were made so that | ||
| 50 | when a definition is created for a new name, a property gets attached | ||
| 51 | to the symbol with the relevant parameters so that we can write out | ||
| 52 | enough information to reconstruct the definition after reading it | ||
| 53 | back. | ||
| 54 | |||
| 55 | After the main definitions are written out, we emit additional forms | ||
| 56 | to fix up charset definitions, face specs, and so on. These don’t | ||
| 57 | have to worry about cross-linked data structures, so breaking them out | ||
| 58 | into separate forms keeps things simpler. | ||
| 59 | |||
| 60 | *** deferred loading | ||
| 61 | |||
| 62 | The standard category table is huge if written out, so we load | ||
| 63 | international/characters indirectly via dumped.elc instead. We could | ||
| 64 | perhaps suppress the variables and functions defined in | ||
| 65 | international/characters from being output with the rest of the Lisp | ||
| 66 | environment. That information should be available via the load | ||
| 67 | history. We would be assuming that no other loaded Lisp code alters | ||
| 68 | the variables’ values; any modified function values will be overridden | ||
| 69 | by the defalias calls. | ||
| 70 | |||
| 71 | Advice attached to a subr can’t be written out and read back in | ||
| 72 | because of the “#<subr...>” syntax; uniquify attaches advice to | ||
| 73 | rename-buffer, so loading of uniquify is deferred until loading | ||
| 74 | dumped.elc, or until we’ve determined that we’re not dumping at all. | ||
| 75 | |||
| 76 | *** efficient symbol reading | ||
| 77 | |||
| 78 | The symbol parser is not terribly fast. It reads one character at a | ||
| 79 | time (which involves reading one or more bytes, and figuring out the | ||
| 80 | possible encoding of a multibyte character) and figuring out where the | ||
| 81 | end of the symbol is; then the obarray needs to be scanned to see if | ||
| 82 | the symbol is already present. | ||
| 83 | |||
| 84 | It turns out that the “#N#” processing is faster. So now there’s a | ||
| 85 | new option to the printer that will use this form for symbols that | ||
| 86 | show up more than once. Parsing “#24#” and doing the hash table | ||
| 87 | lookup works out better than parsing “setplist” and scanning the | ||
| 88 | obarray over and over, though it makes it much harder for a human to | ||
| 89 | read. | ||
| 90 | |||
| 91 | ** Loading the Lisp environment | ||
| 92 | |||
| 93 | The default action to invoke on startup is now to load | ||
| 94 | “../src/dumped.elc”. For experimentation that name works fine, but | ||
| 95 | for installation it’ll probably be something like just “dumped.elc”, | ||
| 96 | found via the load path. | ||
| 97 | |||
| 98 | New primitives are needed to deal with Emacs data that is not purely | ||
| 99 | Lisp data structures: | ||
| 100 | |||
| 101 | + internal--set-standard-syntax-table | ||
| 102 | + define-charset-internal | ||
| 103 | + define-coding-system-internal | ||
| 104 | |||
| 105 | *** Speeding up the reader | ||
| 106 | |||
| 107 | Reading a very large Lisp file (over a couple of megabytes) is still | ||
| 108 | slow. | ||
| 109 | |||
| 110 | While it seems unavoidable that loading a Lisp environment at run time | ||
| 111 | will be at least slightly slower than having that environment be part | ||
| 112 | of the executable image when the process is launched, we want to keep | ||
| 113 | the process startup time acceptably fast. (No, that’s not a precisely | ||
| 114 | defined goal.) | ||
| 115 | |||
| 116 | So, a few changes have been made to speed up reading the large Lisp | ||
| 117 | file. Some of them may be generally applicable, even if the | ||
| 118 | big-elc-file approach isn’t adopted. Others may be too specific to | ||
| 119 | this use case to warrant the additional code. | ||
| 120 | |||
| 121 | + Avoiding substitution recursion for #N# forms when the new object | ||
| 122 | is a cons cell. | ||
| 123 | + Using hash tables instead of lists for forms to substitute. | ||
| 124 | + Avoiding circular object checks in some cases. | ||
| 125 | + Handle substituting into a list iteratively instead of | ||
| 126 | recursively. (This one was more about making performance analysis | ||
| 127 | easier for certain tools than directly improving performance.) | ||
| 128 | + Special-case reading from a file. Avoid repeated checks of the | ||
| 129 | type of input source and associated dispatching to appropriate | ||
| 130 | support routines, and hard-code the file-based calls. Streamline | ||
| 131 | the input blocking and unblocking. | ||
| 132 | + Avoid string allocation when reading symbols already in the | ||
| 133 | obarray. | ||
| 134 | |||
| 135 | * Open Issues | ||
| 136 | |||
| 137 | ** CANNOT_DUMP, purify-flag | ||
| 138 | |||
| 139 | The branch has been rebased onto a recent enough “master” version that | ||
| 140 | CANNOT_DUMP works fairly well on GNU/Linux systems. The branch has | ||
| 141 | now been updated to set CANNOT_DUMP unconditionally, to disable the | ||
| 142 | unexec code. As long as dumped.elc does all the proper initialization | ||
| 143 | like the old loadup.el did, that should work well. | ||
| 144 | |||
| 145 | The regular CANNOT_DUMP build does not work on mac OS, at least in the | ||
| 146 | otherwise-normal Nextstep, self-contained-app mode; it seems to be a | ||
| 147 | load-path problem. See bug #27760. | ||
| 148 | |||
| 149 | Some code still looks at purify-flag, including eval.c requiring that | ||
| 150 | it be nil when autoloading. So we still let the big progn set its | ||
| 151 | value. | ||
| 152 | |||
| 153 | ** Building and bootstrapping | ||
| 154 | |||
| 155 | The bootstrap process assumes it needs to build the emacs executable | ||
| 156 | twice, with different environments based on whether stuff has been | ||
| 157 | byte-compiled. | ||
| 158 | |||
| 159 | In this branch, the executables should be the same, but the dumped | ||
| 160 | Lisp files will be different. Ideally we should build the executable | ||
| 161 | only once, and dump out different environment files. Possibly this | ||
| 162 | means that instead of “bootstrap-emacs” we should invoke something | ||
| 163 | like: | ||
| 164 | |||
| 165 | ../path/to/emacs --no-loadup -l ../path/to/bootstrap-dump.elc ... | ||
| 166 | |||
| 167 | It might also make sense for bootstrap-dump.elc to include the byte | ||
| 168 | compiler, and to byte-compile the byte compiler (and other | ||
| 169 | COMPILE_FIRST stuff) in memory before dumping. | ||
| 170 | |||
| 171 | Re-examine whether the use of build numbers makes sense, if we’re not | ||
| 172 | rewriting the executable image. | ||
| 173 | |||
| 174 | ** installation | ||
| 175 | |||
| 176 | Installing this version of Emacs hasn’t been tested much. | ||
| 177 | |||
| 178 | ** offset builds (srcdir=… or /path/to/configure …) | ||
| 179 | |||
| 180 | Builds outside of the source tree (where srcdir is not the root of the | ||
| 181 | build tree) have not been tested much, and don’t currently work. | ||
| 182 | |||
| 183 | The first problem, at least while bootstrapping: “../src/dumped.elc” | ||
| 184 | is relative to $lispdir which is in the source tree, so Emacs doesn’t | ||
| 185 | find the dumped.elc file that’s in the build tree. | ||
| 186 | |||
| 187 | Moving dumped.elc under $lispdir would be inappropriate since the | ||
| 188 | directory is in the source tree and the file content is specific to | ||
| 189 | the configuration being built. We could create a “lisp” directory in | ||
| 190 | the build tree and write dumped.elc there, but since we don’t | ||
| 191 | currently have such a directory, that’ll mean some changes to the load | ||
| 192 | path computation, which is already pretty messy. | ||
| 193 | |||
| 194 | ** Unhandled aspects of environment saving | ||
| 195 | |||
| 196 | *** unprintable objects | ||
| 197 | |||
| 198 | global-buffers-menu-map has cdr slot set to nil, but this seems to get | ||
| 199 | fixed up at run time, so simply omitting it may be okay. | ||
| 200 | |||
| 201 | advertised-signature-table has several subr entries. Perhaps we could | ||
| 202 | filter those out, dump the rest, and then emit additional code to | ||
| 203 | fetch the subr values via their symbol names and insert them into the | ||
| 204 | hash after its initial creation. | ||
| 205 | |||
| 206 | Markers and overlays that aren’t associated with buffers are replaced | ||
| 207 | with newly created ones. This only works for variables with these | ||
| 208 | objects as their values; markers or overlays contained within lists or | ||
| 209 | elsewhere wouldn’t be fixed up, and any sharing of these objects would | ||
| 210 | be lost, but there don’t appear to be any such cases. | ||
| 211 | |||
| 212 | Any obarrays will be dumped in an incomplete form. We can’t | ||
| 213 | distinguish them from vectors that contain symbols and zeros. | ||
| 214 | (Possible fix someday: Make obarrays their own type.) As a special | ||
| 215 | case of this, though, we do look for abbrev tables, and generate code | ||
| 216 | to recreate them at load time. | ||
| 217 | |||
| 218 | *** make-local-variable | ||
| 219 | |||
| 220 | Different flavors of locally-bound variables are hard to distinguish | ||
| 221 | and may not all be saved properly. | ||
| 222 | |||
| 223 | *** defvaralias | ||
| 224 | |||
| 225 | For variable aliases, we emit a defvaralias command and skip the | ||
| 226 | default-value processing; we keep the property list processing and the | ||
| 227 | rest. Is there anything else that needs to be changed? | ||
| 228 | |||
| 229 | *** documentation strings | ||
| 230 | |||
| 231 | We call Snarf-documentation at load time, because it’s the only way to | ||
| 232 | get documentation pointers for Lisp subrs loaded. That may be | ||
| 233 | addressable in other ways, but for the moment it’s outside the scope | ||
| 234 | of this branch. | ||
| 235 | |||
| 236 | Since we do call Snarf-documentation at load time, we can remove the | ||
| 237 | doc strings in DOC from dumped.elc, but we have to be a little careful | ||
| 238 | because not all of the pre-loaded Lisp doc strings wind up in DOC. | ||
| 239 | The easy way to do that, of course, is to scan DOC and, for each doc | ||
| 240 | entry we find, remove the documentation from the live Lisp data before | ||
| 241 | dumping. So, Snarf-documentation now takes an optional argument to | ||
| 242 | tell it to do that; that cut about 22% of the size of dumped.elc at | ||
| 243 | the time. | ||
| 244 | |||
| 245 | There are still a bunch of doc strings winding up in dumped.elc from | ||
| 246 | various sources; see bug #27748. (Not mentioned in the bug report: | ||
| 247 | Compiled lambda forms get “(fn N)” style doc strings in their bytecode | ||
| 248 | representations too. But because we key on function names, there’s no | ||
| 249 | way to accomodate them in the DOC file.) | ||
| 250 | |||
| 251 | *** locations of definitions | ||
| 252 | |||
| 253 | C-h v shows variables as having been defined by dumped.elc, not by the | ||
| 254 | original source file. | ||
| 255 | |||
| 256 | ** coding system definitions | ||
| 257 | |||
| 258 | We repeatedly iterate over coding system names, trying to reload each | ||
| 259 | definition, and postponing those that fail. We should be able to work | ||
| 260 | out the dependencies between them and construct an order that requires | ||
| 261 | only one pass. (Is it worth it?) | ||
| 262 | |||
| 263 | Fix coding-system-list; it seems to have duplicates now. | ||
| 264 | |||
| 265 | ** error reporting | ||
| 266 | |||
| 267 | If dumped.elc can’t be found, Emacs will quietly exit with exit | ||
| 268 | code 42. Unfortunately, when running in X mode, it’s difficult for | ||
| 269 | Lisp code to print any messages to standard error when quitting. But | ||
| 270 | we need to quit, at least in tty mode (do we in X mode?), because | ||
| 271 | interactive usage requires some definitions provided only by the Lisp | ||
| 272 | environment. | ||
| 273 | |||
| 274 | ** garbage collection | ||
| 275 | |||
| 276 | The dumped .elc file contains a very large Lisp form with most of the | ||
| 277 | definitions in it. Causing the garbage collector to always be invoked | ||
| 278 | during startup guarantees some minimum additional delay before the | ||
| 279 | user will be able to interact with Emacs. | ||
| 280 | |||
| 281 | More clever heuristics for when to do GC are probably possible, but | ||
| 282 | outside the scope of this branch. For now, gc-cons-threshold has been | ||
| 283 | raised, arbitrarily, to a value that seems to allow for loading | ||
| 284 | “dumped.elc” on GNU/Linux without GC during or immediately after. | ||
| 285 | |||
| 286 | ** load path setting | ||
| 287 | |||
| 288 | Environment variable support may be broken. | ||
| 289 | |||
| 290 | ** little niceties | ||
| 291 | |||
| 292 | Maybe we should rename the file, so that we display “Loading | ||
| 293 | lisp-environment...” during startup. | ||
| 294 | |||
| 295 | ** bugs? | ||
| 296 | |||
| 297 | The default value of charset-map-path is set based on the build tree | ||
| 298 | (or source tree?), so reverting via customize would probably result in | ||
| 299 | a bogus value. This bug exists in the master version as well when | ||
| 300 | using unexec; in CANNOT_DUMP mode (when the Lisp code is only loaded | ||
| 301 | from the installed tree) it doesn’t seem to be a problem. | ||
| 302 | |||
| 303 | ** other changes | ||
| 304 | |||
| 305 | Dropped changes from previous revisions due to merge conflicts; may | ||
| 306 | reinstate later: | ||
| 307 | |||
| 308 | + In lread.c, substitute in cons iteratively (on “cdr” slot) instead | ||
| 309 | of recursively. | ||
| 310 | + In lread.c, change “seen” list to hash table. | ||
| 311 | + In lread.c, add a separate read1 loop specialized for file reading, | ||
| 312 | with input blocking manipulated only when actually reading from the | ||
| 313 | file, not when just pulling the next byte from a buffer. | ||