about summary refs log tree commit diff
path: root/compilation/execution.e
blob: 01f3b6809366bcd42f4a252d1d961a3d44f42e8a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
~ ~~~~~~~~~~~~~~~~~~~~~
~ ~~ Execution model ~~
~ ~~~~~~~~~~~~~~~~~~~~~
~
~    We use Forth-style dual stacks, one for values and one for control. We
~  use rsp for values, just like C does. We use rbp for the control stack,
~  which is a special Forth-y stack: These are pointers into the bodies of
~  Forth words, not return addresses.
~
~    The choice of rsp and rbp for the stack pointers imitates Jonesforth;
~  I'm hopeful that it gives us convenient addressing modes, and will report
~  back about that when I feel that I understand the implications.
~
~    In Forth, everything is a "word", including mutable variables.
~  Conceptually, a word is a unit of execution, which may be implemented
~  either in machine code or as an array of pointer to other words.
~
~    This polymorphism is implemented by having each word's contents begin
~  with a "codeword", which is a pointer to machine code that "interprets"
~  the rest of the contents. In the case of words implemented in machine
~  code, the codeword points directly to that code, which is normally right
~  next to it.
~
~    Variables, to Forth, are simply one more thing that can be executed; the
~  effect of executing a variable is to push its address onto the value
~  stack.
~
~    We adopt this model of words, codewords, and variables-as-words. It's
~  really nice how it doesn't force anything else on us, not even a heap,
~  though we do end up using the log as a sort-of heap.
~
~    We specifically implement a version of calling and returning that Forth
~  calls indirect threaded code: The control stack is a stack of pointers
~  into the middle of interpreted words. The interpreter snippet, called
~  docol, implements calling.  Each word is responsible for making sure
~  returning works properly. Interpreted words accomplish this by ending with
~  the word "exit", while machine-code words accomplish it by ending with a
~  verbatim snippet called "next".
~
~    Conceptually, "next" returns, but more specifically it accomplishes this
~  by doing the caller's next dispatch for it; thus control never actually
~  goes back to the caller's interpreter after initial setup. For performance
~  reasons, "next" is always inlined, so we define it as a macro.
~
~    The docol routine is just ordinary code, not a macro. It's defined later
~  in this file, as a label.
~
~    Notionally, we could consider not having a dictionary, and not giving
~  our words names. However, it feels silly to stop when we're so close to
~  being a full Forth, and using names for things solves a bootstrapping
~  problem related to log management - see the write-up of cold-start about
~  how the log is created, below. So, we add an additional header before the
~  codeword for this purpose.
~
~    The Forth dictionary is usually a linked list of every word that has
~  ever been defined, with the newest at the head; the names of words are
~  stored in string fields, often right next to the link pointer. We adopt
~  this model, with the field sizes and order shown in the quick reference
~  below. We break with Forth tradition in one way: Rather than having a
~  length field, we use a null-terminated string. Thus, there's no length
~  limit on names. This necessitates breaking out the flags (to be explained
~  later) into their own byte, rather than taking bits from the length field
~  for them.
~
~    There's an important performance consideration: Executable words
~  reference each other by pointers to their respective codewords. However,
~  dictionary entries reference each other by pointers to their respective
~  link fields. Traversing from the link field to the codeword is easy,
~  though it's a non-constant-time operation: Just walk the string. In order
~  to make Forth words easy to "decompile", it would be nice to also have a
~  way to traverse backwards. We solve this by making the name field be
~  null-terminated at both ends. Fun, yeah?
~
~
~
~
~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~   Quick Reference
~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~
~  The layout of an interpreted word:
~
~  (overall start)
~             0x00 - 0x07                     Link (to next-oldest word)
~             0x08 - 0x08  HM00000I           Flags
~                                                 H - hidden
~                                                 M - metadata
~                                                 I - immediate
~                                                 all other bits reserved
~  (name start)
~             0x09 - 0x09                     Null byte (terminates name)
~             0x0a - name-end - 1             Name, as UTF-8
~         name-end - name-end                 Null byte (terminates name)
~  (padding start)
~     name-end + 1 - codeword-start - 1       Zero-pad to 8-byte boundary
~     (it's possible this will be zero bytes long)
~  (codeword start)
~       ... + 0x00 - ... + 0x08               Codeword (ie. address of docol)
~           (8-byte chunks)                   Addresses of other words
~                  - ... (end)                Address of "exit" word
~
~  The layout of a machine-code word is different only from the codeword on:
~
~       ... + 0x00 - ... + 0x08               Addresss of next byte
~       ... + 0x08 - ????                     Arbitrary machine code
~                  - ... (end)                Inlined implementation of next
~
~  Also, words always start at 8-byte boundaries.
~
~
~  REGISTER usage conventions:
~
~  * rsi is the "instruction pointer" for the "interpreter".
~      That is, it points to some word-pointer inside an array of
~    word-pointers inside the content of the word they're part of. It always
~    points to the next word that should be executed, whose execution hasn't
~    begun yet.
~
~  * rbp points to the top of the control stack
~      These are former values of rsi, to eventually be returned to, from
~    successively older callers as you look further up the stack. The stack
~    grows downwards in memory. Since values are kept separately, the only
~    thing on the control stack is return addresses, one per layer of call.
~
~  * rsp points to the top of the value stack
~      The value stack has no specific format, but it grows downwards in
~    memory. In particular there's no concept of stack frames, because items
~    on the stack don't belong to any particular word; the value stack in
~    Forth is in part a mechanism for passing values between words.
~
~  Additionally, immediately after beginning execution of a word:
~
~  * rax points to the address of the codeword being executed
~      The value of rax is purely for the callee's benefit, and does not need
~    to be preserved.
~
~    Other registers are purely discretionary, and are not preserved across
~  calls.
~
~
~  FLAG usage:
~
~  * DF should be 0
~    We use lodsq extensively and that makes it increment rsi after using it.
~
~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

~   The macros next, beforenext, pushcontrol, and popcontrol are implemented
~ in language/static/execution-support.e. It's a good idea to go read about
~ them now.
~
~ TODO the following high-level summary should go... somewhere?
~   When a docol word is invoked, it immediately does pushcontrol (see
~ execution-support.e) to push rsi onto the control stack, whose top is
~ indicated by rbp. When an assembly word is invoked, it leaves rsi and rbp
~ alone; assembly words don't normally call other words, so there's no need
~ for them to appear on the control stack at all.
~
~   When an assembly word returns, it looks for an execution token pointed
~ to by rsi, increments rsi, and invokes that word as if calling it. This
~ is the threaded calling approach to Forth execution. When a docol word
~ returns, it calls exit, which needs to remove itself from the control
~ stack, so exit pops the top of the control stack into rsi to get back into
~ its caller's context, then does the same steps an assembly word would do.


~ Constants
~ ~~~~~~~~~
~
~ These are used in cold-start, just below.

: log-requested-address 0x0000001000000000  ; ~ (very arbitrary)
: log-size              0x0000000001000000  ; ~ 16 MiB
: control-stack-size                0x10000 ; ~ 64 KiB


~ Routine cold-start
~ ~~~~~~~~~~~~~~~~~~
~
~   This is the entry point of the whole program, the very first code we
~ actually execute. Linkers traditionally call this _start, but let's live a
~ little. Anyway, the ELF header points to it and exec() jumps to it; from the
~ kernel's perspective, there's no such thing as names. Also, though this code
~ could be anywhere in the code part of the output, in order to make the
~ hexdump pretty we put it at the start.
~
~   The kernel gives us most registers zeroed, and rsp pointing to the
~ command-line stuff (argc, argv, envp), which is at an ASLR'd address with
~ some stack space allocated for us, despite the fact we didn't request any.
~ It also gives us all the flags clear except IF, but we don't rely on that.
~ Lastly, of course, it loads our code segment and sets the instruction
~ pointer where we asked; we don't need to check what those addresses are,
~ because they're not randomized.
~
~   Notably, though cold-start points rbp to the control stack, it leaves
~ the control stack empty, so trying to return will pop a value that doesn't
~ exist, and crash. Eventually, the bottom value on the control stack will
~ be the word "quit", which loops forever and is the closest thing Forth has
~ to a "main" word, but it's up to warm-start to make that happen.
~
~   Cold-start is really only responsible for one-time initialization.
~
~
~
~ Registers in:
~
~ * rsp points to the logical top  of the value stack
~     The kernel sets this up for us, and we need to save it somewhere so
~   Forth can use it.
~
~ Registers out:
~
~ * rsi points within "warm-start"
~ * rsp points to the top of the value stack
~ * rbp points to the top of the control stack
~
~ Stack in:
~
~ * argc, argv, envp in the usual Unix way
~     We ignore them, though.
~
~ Stack out:
~
~ * The value of "log", as a pointer
~     The meaning of this will be explained below.
~
~ Registers within:
~
~ * rdi points to the base the log was allocated at, once it exists
~     This is the same value that "log" will hold, once we reach a point
~   where we have variables. Of course, variables are stored on the log,
~   hence this temporary measure.
~
~   We also take this opportunity to define soeme memory layout parameters
~ that this routine will be responsible for doing something with:
~
~ (output memory start, current output point
~  -- output memory start, current output point)
: output-cold-start
  ~ : blank-line
  ~ : blank-line
  ~ : ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
  ~ :  ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
  ~ : ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
  ~ : ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  ~ : blank-line
  ~ :   This is the start of the cold-start routine, which is the first code
  ~ : of any kind the executable runs. It has the responsibility of creating
  ~ : the Forth execution environment, then invoking the warm-start routine,
  ~ : which is defined below.
  ~ : blank-line
  ~ : indent

  current-offset L' cold-start set-label
  cld                                      ~ clear the DF flag

  ~   Prepare the log.
  ~
  ~   We could ask for a data segment in the program header, but where's the
  ~ fun in that? Instead, we call mmap().
  ~
  ~   If we wanted the kernel to do ASLR for us, passing address zero would
  ~ cause it to pick somewhere at random, but instead we choose our own
  ~ location. It's still not guaranteed to be where we ask for, so we still
  ~ do the work to record where it wound up. We could pass the "fixed" flag
  ~ and the kernel would trust us, but this gives us more options for
  ~ interoperating with other runtimes.
  ~
  9 :rax mov-reg64-imm64                  ~ :   mmap()
  log-requested-address :rdi mov-reg64-imm64
                                          ~ : 0   address (very arbitrary)
  log-size :rsi mov-reg64-imm64           ~ :   size (one meg)
  0x07 :rdx mov-reg64-imm64
                                          ~ : 0   protection (read+write+exec)
  0x22 :r10 mov-extrareg64-imm64          ~ :   flags (private+anonymous)
  0 :r8 mov-extrareg64-imm64              ~ :   file descriptor (ignored)
  0 :r9 mov-extrareg64-imm64              ~ :   offset (ignored)
  syscall

  ~   The return value of the system call is in rax, we'll use it in a sec.
  ~ We need to save this somewhere in case we ever want to munmap() it;
  ~ there's no widely-used name for it so we have to make one up. S0 and r0
  ~ are widely-used names for the physical tops (logical bottoms) of the
  ~ value and control stacks, respectively, and we will eventually set those
  ~ up as well, so we should keep those names in mind. The control stack
  ~ lives within the log, while the value stack is its own segment. This
  ~ value, though, is the physical bottom of the segment, meaning that it
  ~ stays the same even as we allocate and deallocate things within it. This
  ~ is unlike the two stack pointers, so we give it a name that doesn't
  ~ suggest similarity: "log".
  ~
  ~   Once Forth is fully set up, its internal variables will be accessed
  ~ through variable-words like any other Forth data, including "log". To
  ~ get to that point, though, we need to be able to hold onto variable data
  ~ between now and then. In fact, if we don't have at least one of "log"
  ~ and "here" (its counterpart which points to the logical top end), all
  ~ our efforts to hold onto anything seem a bit doomed.
  ~
  ~   So, we temporarily dedicate rdi to "log" - only within this routine -
  ~ and store everything else in ways that let us find things by reference
  ~ to it. We choose rdi because it works with the indexing modes we care
  ~ about, and its name suggests its function.
  ~
  ~   The strategy Jonesforth uses is not applicable to us; Jonesforth
  ~ takes advantage of the linker to let its code segment refer to specific,
  ~ pre-allocated objects in the data segment. We are our own linker, and we
  ~ don't care to have a data segment. Hence, this approach.
  ~
  ~   Keying things off "log" is the fundamental decision, but to make sure
  ~ our variables are accessible both during early bootstrapping, and later,
  ~ we also have to be thoughtful about data structures. More on that in a
  ~ moment.
  ~
  :rax :rdi mov-reg64-reg64

  ~   We also initialize rbp. We could hold off and let "quit" do this, but
  ~ doing it now is the easiest way to initialize the r0 variable, since
  ~ there's no instruction that moves a 64-bit immediate to memory.
  ~
  ~   This is the moment at which we decide where the control stack starts!
  ~ Fun, right? "Allocation" is just a fancy word for picking where we want
  ~ something, then being consistent about it - like placing furniture in
  ~ your home. See below for a little more thought about why here in
  ~ particular.
  ~
  :rdi control-stack-size :rbp lea-reg64-disp32-reg64

  ~   Now we save some stuff onto the log. These are the locations that
  ~ will eventually be the backing stores of the Forth variables, but we
  ~ don't create the word headers yet, since there's no requirement that
  ~ they be next to the backing stores. We'll do that later, once we have
  ~ word-writing infrastructure in place. For now, we just use their offsets
  ~ relative to the physical bottom of the log, which are fixed.
  ~
  ~   These will be the permanent homes of these values, though we have
  ~ copies of them elsewhere while we're still in this routine.
  ~
  :rdi control-stack-size 0x00 + :rdi mov-reg64-disp32-reg64  ~ log
  :rsp control-stack-size 0x08 + :rdi mov-reg64-disp32-reg64  ~ s0
  :rbp control-stack-size 0x10 + :rdi mov-reg64-disp32-reg64  ~ r0
  L@' final-word-name :rax mov-reg64-imm64
  :rax control-stack-size 0x18 + :rdi mov-reg64-disp32-reg64  ~ latest
  :rdi control-stack-size 0x28 + :rax lea-reg64-disp32-reg64
  :rax control-stack-size 0x20 + :rdi mov-reg64-disp32-reg64  ~ here
  ~
  ~ * "log" is the physical bottom of the log
  ~     The log grows upwards in memory, so this is also the logical
  ~   bottom. This comes from the address mmap() just returned to us.
  ~ * "s0" is the logical bottom of the value stack
  ~     The value stack grows downwards in memory, so this is the physical
  ~   top of it. This comes from the stack pointer the kernel initialized us
  ~   with.
  ~ * "r0" is the logical bottom of the control stack
  ~     The control stack also grows downwards, so this is its pysical top
  ~   as well. We allocate this dedicated space within the log right here,
  ~   in this routine, through our choice of where to put things.
  ~ * "here" is the physical start of the unallocated space in the log
  ~     We allocate log space from bottom to top, by incrementing this
  ~   value. So, it would also be accurate to say that it points immediately
  ~   after the physical top of the allocated space. At any rate, the
  ~   address it points to is the first one that hasn't been used yet.
  ~ * "latest" is the address of the most-recently-defined word's header
  ~     Defining new words changes this value.
  ~
  ~   s0 and r0 are mostly used when we want to initialize or reinitialize
  ~ their respective stacks - that is, discard all their contents at once.
  ~
  ~   The value of r0 is the same address these variables start at, so
  ~ you'll want to do a close read of the implementation of pushcontrol
  ~ and convince yourself that it only ever writes things just below the rbp
  ~ address it receives, never right on top of it.
  ~
  ~   As an aside, by the way, please notice that strictly speaking, r0
  ~ could be a constant... but it isn't known until runtime, so we might as
  ~ well make it a variable. That will play nicely with any astonishing
  ~ memory shenanigans someone might wish to do in the future.
  ~
  ~   Notice also that "here" points immediately after itself. This is just
  ~ a convenience, making it the last one like that so that the concern is
  ~ dealt with in a single place and is easy to keep up-to-date with code
  ~ changes.
  ~
  ~   A little more detail about why we offset everything by
  ~ control_stack_size: We're carving out some space at the bottom of the
  ~ log - which grows low-to-high - to be the control stack - which grows
  ~ high-to-low. So the control stack is allocated out of the log as a
  ~ fixed-size, one-time thing, and then the variables come immediately
  ~ after that. We do need to use 32-bit displacement indexing to access
  ~ them this way, but that's no big deal.
  ~
  ~   This is perhaps questionable, they should maybe be separate segments
  ~ created with separate calls to mmap(), but for now we're not worried
  ~ about overflow so we use the same allocation for both.
  ~
  ~   We'll come back to these variables a bit later and generate the word
  ~ headers that point at them, but now we're almost ready to switch to
  ~ proper threaded-execution, so we finish that setup first...

  ~   Push the value of "log" onto the value stack so that it can be the
  ~ breadcrumb the threaded code needs to find... the backing store of
  ~ "log". Yes, self-reference can be weird like that sometimes. There's
  ~ nothing stopping "quit" from reading rdi, it just violates the
  ~ abstraction...
  :rdi push-reg64

  ~   We are about to set up rsi, we did rbp already, and rsp came to us
  ~ already set up. That's all that "next" needs, so take it away!
  L@' warm-start L@' origin + :rsi mov-reg64-imm64
  pack-next
  ~ : deindent
  ;


~ Routine warm-start
~ ~~~~~~~~~~~~~~~~~~
~
~   This routine runs as Forth code. That is, indirect threaded execution has
~ been initialized, and the structure of this routine is an array of codeword
~ pointers. It was directly jumped to from cold-start. There's nowhere to
~ return to, so it needs to never return.
~
~   There are a few optional pieces of the warm-start routine, so here in the
~ compile-time code that builds it, we break the routine into logical pieces
~ which are assembled by calling the appropriate helpers sequentially.
~
~   Outputting the warm-start routine should always begin by calling
~ output-warm-start-header.
~
~ (output buffer start, output point -- output buffer start, output point)
: output-warm-start-header
  ~ : blank-line
  ~ : blank-line
  ~ : ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
  ~ :  ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
  ~ : ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
  ~ : ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  ~ : blank-line
  ~ :   This is the start of the warm-start routine, which is the first Forth
  ~ : code the executable runs, after the cold-start routine has created the
  ~ : Forth execution environment.
  ~ :
  ~ :   It does some initial setup here at the top; then the vast bulk of it
  ~ : is actually the log-load routine, directly included; then finally there
  ~ : is a small amount of finishing work done at the tail end of the
  ~ : warm-start routine.
  ~ :
  ~ :   The routine's body is interpreted Forth code, meaning that it is an
  ~ : an array of pointers to the codewords of Forth words, occasionally
  ~ : interspersed with literal values.
  ~ :
  ~ :   The codeword pointers were located by using the label system, which
  ~ : means the labels they reference must have been defined somewhere else,
  ~ : normally by the label transform. The label-transformed code normally
  ~ : occurs after the warm-start routine in the executable, which means these
  ~ : are forward references to words defined far below.
  ~ : blank-line
  ~ : indent

  ~   While it's not actually a requirement that codeword pointers be
  ~ word-aligned, it's highly likely that it helps performance. (Whether it
  ~ does is up to Intel's microcode.)
  8 packalign
  current-offset L!' warm-start
  ~ (input string pointer, output buffer start, output point)

  ~   Before handing off to us, cold-start pushed a single value onto the
  ~ stack, a pointer to the beginning of the log. Now, we load our entire
  ~ Forth implementation onto that log, beginning with the minimal set of
  ~ words needed to define more words. We do this because we need variables as
  ~ infrastructure so we can eventually have dynamic definitions.
  ~
  ~   There's something non-obvious here: words implemented statically as
  ~ part of the executable image can't contain things that vary at runtime.
  ~ That means that even if these words tried to implement some sort of
  ~ dynamic lookup, they would have no way to find the root of whatever
  ~ dynamic data structure they use. Dynamism needs to be bootstrapped.
  ~
  ~   In a more traditional C-style program, static code could look up
  ~ variables based on fixed addresses that are the same on every run.
  ~ Failing that, we could dedicate a register to it, though that's a
  ~ considerable expense. We chose not to do either of those things, because
  ~ we want the versatility that comes with not being picky about our
  ~ address space: It allows us to contemplate future improvements such as
  ~ ASLR, or embedding into other processes that impose their own addressing
  ~ constraints, or even coexisting with multiple versions of ourselves.
  ~ That choice does mean we have the hard version of this bootstrapping
  ~ problem, and copying ourselves to the log is how we solve it.
  ~
  ~   We do have the log address right now, though that won't last. In case
  ~ it's unclear why not: keeping it on the stack would require all future
  ~ references to walk the stack, and somehow know when they've reached the
  ~ bottom. The stack is a good place to keep things with clearly delimited
  ~ lifetimes and visibility, but when we want something to live for our
  ~ entire program and be easy to find from any code within it, we need to
  ~ do something else. Anyway, since we have the address, we can use it for
  ~ the next little bit of setup. We have a bunch of helper words, from
  ~ language/static/log-load.e, which make this easier.
  ~
  ~   The first few words we define are our variables, which hardcode the
  ~ addresses they will return - but since we're doing this at runtime,
  ~ "hardcoding" can reflect where our log is. This is the fundamental
  ~ trick that makes the log usable.
  ~
  ~   One more thing to notice: We already allocated the backing stores of
  ~ these variables, and populated their initial values, in cold-start. The
  ~ words we're defining return those same addresses for the same backing
  ~ stores. So, we have continuity: Stuff defined in terms of the
  ~ variable-words we're defining now will interoperate with the stuff that
  ~ we define using the log-load helpers, which includes those very words.
  ~ Both the log-load code and the later code are dealing with the same data
  ~ structures, they're just using a different technique to find them.
  ~
  ~   This is the only hardcoding we need to do; by building on top of it,
  ~ we will soon reach a point where the rest of the system can be defined
  ~ within itself.

  L@' log-load-log offset-to-target-address-space pack64
  ~ : -8 log-load-log #-- (codeword pointer)
  L@' litstring offset-to-target-address-space pack64
  ~ : -8 litstring #-- (codeword pointer)
  s" log" packstring 8 packalign
  L@' log-load-variable offset-to-target-address-space pack64
  ~ : -8 log-load-variable #-- (codeword pointer)

  L@' log-load-s0 offset-to-target-address-space pack64
  ~ : -8 log-load-s0 #-- (codeword pointer)
  L@' litstring offset-to-target-address-space pack64
  ~ : -8 litstring #-- (codeword pointer)
  s" s0" packstring 8 packalign
  L@' log-load-variable offset-to-target-address-space pack64
  ~ : -8 log-load-variable #-- (codeword pointer)

  L@' log-load-r0 offset-to-target-address-space pack64
  ~ : -8 log-load-r0 #-- (codeword pointer)
  L@' litstring offset-to-target-address-space pack64
  ~ : -8 litstring #-- (codeword pointer)
  s" r0" packstring 8 packalign
  L@' log-load-variable offset-to-target-address-space pack64
  ~ : -8 log-load-variable #-- (codeword pointer)

  L@' log-load-latest offset-to-target-address-space pack64
  ~ : -8 log-load-latest #-- (codeword pointer)
  L@' litstring offset-to-target-address-space pack64
  ~ : -8 litstring #-- (codeword pointer)
  s" latest" packstring 8 packalign
  L@' log-load-variable offset-to-target-address-space pack64
  ~ : -8 log-load-variable #-- (codeword pointer)

  L@' log-load-here offset-to-target-address-space pack64
  ~ : -8 log-load-here #-- (codeword pointer)
  L@' litstring offset-to-target-address-space pack64
  ~ : -8 litstring #-- (codeword pointer)
  s" here" packstring 8 packalign
  L@' log-load-variable offset-to-target-address-space pack64
  ~ : -8 log-load-variable #-- (codeword pointer)

  ~   Having done that, nothing else needs to be defined in an unusual way, so
  ~ at this point the warm-start routine can go ahead and dispatch to the
  ~ log-load transform, and do the rest of the code through that. However, to
  ~ allow customization, we here in output-warm-start will let our caller take
  ~ responsibility for making sure that happens.
  ;


~   This helper doesn't have any actual compile-time responsibilities, it
~ exists solely for the sake of the magic comments.
~
~   We could have it be responsible for calling log-load-transform, but
~ honestly it's nicer letting the caller be responsible for that, since the
~ caller is also responsible for calling label-transform.
~
~ (output buffer start, output point -- output buffer start, output point)
: output-warm-start-post-log-load-transform
  ~ : blank-line
  ~ :   We're now done with the log-load routine; the next little bit of code
  ~ : here is the final bit of the warm-start routine.
  ;



~   After the log-load routine finishes, everything we need has been added to
~ the log and we're almost ready to jump into dynamic code running from the
~ log. That code still needs main-input-buffer to be set up in some way. The
~ simplest approach is to attach it directly to standard input. This helper
~ outputs a snippet that does that, structured to be used as part of the
~ warm-start routine.
~
~   Executables that don't call output-warm-start-attach-stdin will need to
~ instead call output-warm-start-attach-boot-source. Exactly one of these must
~ be used.
~
~ (output buffer start, output point -- output buffer start, output point)
: output-warm-start-attach-stdin
  L@' litstring offset-to-target-address-space pack64
  ~ : -8 litstring #-- (codeword pointer)
  s" relink-main-input-buffer-to-stdin" packstring 8 packalign
  L@' log-load-find-execution-token offset-to-target-address-space pack64
  ~ : -8 log-load-find-execution-token #-- (codeword pointer)
  L@' execute offset-to-target-address-space pack64
  ~ : -8 execute #-- (codeword pointer)
  ;


~   After the log-load routine finishes, everything we need has been added to
~ the log and we're almost ready to jump into dynamic code running from the
~ log. It's inconvenient for code under the log-load transform to interact
~ with the label system, so if we want to point main-input-buffer at a string,
~ that's the place to do it.
~
~   That's optional functionality, which any given executable may or may not
~ want to use, so it's here in this helper. If your executable is built with
~ a call to output-warm-start-attach-boot-source, it must also at some point
~ include a call to output-boot-source.
~
~ (output buffer start, output point, input string pointer
~  -- output buffer start, output point)
: output-warm-start-attach-boot-source
  L@' litstring offset-to-target-address-space pack64
  ~ : -8 litstring #-- (codeword pointer)
  s" main-input-buffer" packstring 8 packalign
  L@' log-load-find-execution-token offset-to-target-address-space pack64
  ~ : -8 log-load-find-execution-token #-- (codeword pointer)
  L@' execute offset-to-target-address-space pack64
  ~ : -8 execute #-- (codeword pointer)
  L@' lit offset-to-target-address-space pack64
  ~ : -8 lit #-- (codeword pointer)
  L@' boot-source offset-to-target-address-space pack64
  ~ : -8 boot-source #-- (data pointer)
  L@' 3roll offset-to-target-address-space pack64
  ~ : -8 3roll #-- (codeword pointer)
  L@' litstring offset-to-target-address-space pack64
  ~ : -8 litstring #-- (codeword pointer)
  s" attach-string-to-input-buffer" packstring 8 packalign
  L@' log-load-find-execution-token offset-to-target-address-space pack64
  ~ : -8 log-load-find-execution-token #-- (codeword pointer)
  L@' swap offset-to-target-address-space pack64
  ~ : -8 swap #-- (codeword pointer)
  L@' lit offset-to-target-address-space pack64
  ~ : -8 lit #-- (codeword pointer)
  4 ~ : provide-hex
  pack64
  ~ : -8 # #-- (integer literal)
  L@' unroll offset-to-target-address-space pack64
  ~ : -8 unroll #-- (codeword pointer)
  L@' execute offset-to-target-address-space pack64
  ~ : -8 execute #-- (codeword pointer)
  ;


~   Once the log-load routine has been run and main-input-buffer has been
~ prepared, the next layer is built. This helper outputs the final little bit
~ of the warm-start routine, which moves on to that layer.
~
~   Outputting the warm-start routine should always end by calling
~ output-warm-start-footer.
~
~ (output buffer start, output point -- output buffer start, output point)
: output-warm-start-footer
  L@' litstring offset-to-target-address-space pack64
  ~ : -8 litstring #-- (codeword pointer)
  s" quit" packstring 8 packalign
  L@' log-load-find-execution-token offset-to-target-address-space pack64
  ~ : -8 log-load-find-execution-token #-- (codeword pointer)
  L@' swap offset-to-target-address-space pack64
  ~ : -8 swap #-- (codeword pointer)
  ~ : Get rid of that heap pointer on the stack, we're finally done with it!
  L@' drop offset-to-target-address-space pack64
  ~ : -8 drop #-- (codeword pointer)
  L@' execute offset-to-target-address-space pack64
  ~ : -8 execute #-- (codeword pointer)
  ~ : deindent
  ;


~   This helper generates a single word entry named "boot-source", flagged as
~ hidden, and outputs a string as its contents. This string is meant to be
~ used by the code generated by output-warm-start-attach-boot-source, and will
~ have no significant effects otherwise.
~
~   The helper is expecting to be called at a point where the generated output
~ already has a dictionary, and isn't in the middle of generating any
~ routines. The usual place to call it is immediately after the label
~ transform.
~
~ (output buffer start, output point, previous entry pointer,
~  input string pointer -- output buffer start, output point)
: output-boot-source
  ~ : blank-line
  ~ : blank-line
  ~ : ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
  ~ :  ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
  ~ : ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
  ~ : ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  ~ : blank-line
  ~ :   This is the start of the boot-source, which is Forth code included
  ~ : as text, and invoked by the warm-start routine. At the time it runs,
  ~ : the warm-start routine and the log-load routine have both completed and
  ~ : the Forth execution environment is fully ready.
  ~ :
  ~ :   The routine is stored as a dictionary entry with the "hidden" flag
  ~ : set. It has a regular entry header up to, but not including, the
  ~ ; codeword. Then it has text, with a null terminator and alignment
  ~ : padding at the end.
  ~ :
  ~ :   Making this readable in the hex dump is no small task, but has been
  ~ : valiantly attempted.
  ~ : blank-line
  ~ : indent
  3unroll pack64 ~ : 8 #-- (previous entry pointer)
  ~ : 1 #-- (entry flags)
  0x80 pack8
  ~ : 2 bidirectional-null-terminated entry name
  0 pack8
  ~ (output buffer start, input string pointer, output point)

  s" boot-source"
  ~   We do a spurious stringlen just so the magic comments can see the
  ~ string's length and use it for an appropriate adjustment.
  dup stringlen
  ~ : provide-data
  ~ : data-adjust-length
  drop
  ~ : 2 suppress
  packstring
  8 packalign
  ~ : fresh-line
  ~ (output buffer start, input string pointer, output point)

  dup 3 pick - L!' boot-source

  swap packstring 8 packalign
  ~ : deindent
  ;


~ Where next?
~ ~~~~~~~~~~~
~
~   The definitions of "docol" and "exit" are very tightly bound up with the
~ execution model. They're defined and explained in language/static/core.e,
~ because they need to be part of the build process in two different ways,
~ like the other core functionality.