-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathembed.fth
4838 lines (4359 loc) · 208 KB
/
embed.fth
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
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\ # embed.fth
\
\ | Project | A Small Forth VM/Implementation |
\ | ---------- | --------------------------------- |
\ | Author | Richard James Howe |
\ | Copyright | 2017-2018 Richard James Howe |
\ | License | MIT |
\ | Email | howe.r.j.89@gmail.com |
\ | Repository | <https://github.com/howerj/embed> |
\
\ ## A Meta-compiler, an implementation of eForth, and a tutorial on both.
\
\ ## Introduction
\
\ In this file a meta-compiler (or a cross compiler written in [Forth][]) is
\ described and implemented, and after that a working Forth interpreter
\ is both described and implemented. This new interpreter can be used in turn
\ to meta-compile the original program, ad infinitum. The design decisions
\ and the philosophy behind Forth and the system will also be elucidated.
\
\ This files source is a Forth program which the meta-compiler can read, it
\ is also used as source for a simple document generation system using [AWK][]
\ which feeds into either [Pandoc][] for [PDF][] or the original [Markdown][]
\ script for [HTML][] output. The AWK script is crude and requires that the
\ Forth source file, [embed.fth][] be formatted in a specific way.
\
\ Lines beginning with a back-slash are turned into normal Markdown text, with
\ some characters needed to be escaped. Other lines are assumed to be Forth
\ source code and are turned into Markdown code/literal-text blocks with
\ a number and '|' symbol preceding them, numbering starts from line '0001'.
\
\ ### What is Forth?
\
\ Forth is a stack based procedural language, which uses Reverse Polish
\ Notation (RPN) to enter expressions. It is a minimal language with no type
\ checking and little to no error handling depending on the implementation.
\ Despite its small size and simplicity it has various features usually found
\ in higher level languages, such as reflection, incremental compilation and
\ an interactive read-evaluate-print loop.
\
\ It is still at heart a language that is close to the machine with low
\ level capabilities and direct access to memory. Memory manage itself is
\ mostly manual, with preallocation of all needed memory the preferred method
\ of program writing.
\
\ Forth has mostly fallen out of favor in recent years, it performed admirably
\ on the microcomputer systems available in the 1980s and is still suitable
\ for very memory constrained embed systems (having on a few kilobytes of
\ memory available to them), but lacks a lot of features modern languages
\ provide.
\
\ A catalogue of deficiencies hamper Forth adoption; poor string handling,
\ lack of libraries and poor integration with the operating system (on hosted
\ platforms), mutually incompatible and wildly different Forth implementations
\ and as mentioned - little error detection and handling.
\
\ Despite this fact it has a core of adherents who find uses for the language,
\ in fact some of its deficiencies are actually trade-offs. Having no type
\ checking means there is no type checking to do, having very little in the way
\ of error detection means errors do not have to be detected. This off loads
\ the complexity of the problem to the programmer and means a Forth
\ implementation can be minimal and terse.
\
\ The saying "Once you have seen one Forth implementation, you have seen
\ one Forth implementation" comes about because of how easy it is to implement
\ a Forth, which is a double edged sword. It is possible to completely
\ understand a Forth system, the software, the hardware and the problems you
\ are trying to solve and optimize everything towards this goal. This is oft
\ not possible with modern systems, a single person cannot totally understand
\ even subcomponents of modern systems in its entirety (such as compilers
\ or the operating system kernels we use).
\
\ Another saying from the creator of Forth, Charles Moore,
\ "Forth is Sudoku for programmers". The reason the author uses Forth is
\ because it is fun, no more justification is needed.
\
\ ### Project Origins
\
\ This project derives from a simulator for a [CPU written in VHDL][], designed
\ to execute Forth primitives directly. The CPU and Forth interpreter
\ themselves have their own sources, which all makes for a confusing pedigree.
\ The CPU, called the H2, was derived from a well known Forth CPU written
\ in [Verilog][], called the [J1 CPU][], and the Forth running on the H2 comes
\ from an adaption of [eForth written for the J1][] and
\ from 'The Zen of eForth' by C. H. Ting.
\
\ Instead of a metacompiler written in Forth a cross compiler for a Forth like
\ language was made, which could create an image readable by both the
\ simulator, and the FPGA development tools. The simulator was cut down and
\ modified for use on a computer, with new instructions for input and output.
\
\ This system, with a cross compiler and virtual machine written in C, was
\ used to develop the present system which consists of only the virtual
\ machine, a binary image containing a Forth interpreter, and this metacompiler
\ with the meta-compiled Forth. These changes and the discarding of the cross
\ compiler written in C can be seen in the Git repository this project comes
\ in (<https://github.com/howerj/embed>). The VM here is no longer compatible
\ with the [H2 CPU][] it was derived from.
\
\ The project, documentation and Forth images are under an [MIT license][].
\
\ ### The Virtual Machine
\
\ The virtual machine is incredibly simple and cut down at around 200 lines of
\ C code, with most of the code being not being the virtual machine itself,
\ but code to get data in and out of the system correctly, or setting the
\ machine up. It is described in the appendix (at the end of this file), which
\ also contains an example implementation of the virtual machine.
\
\ The virtual machine is 16-bit dual stack machine with an instruction set
\ encoding which allows for many Forth words to be implemented in a single
\ instruction. As the CPU is designed to execute Forth, Subroutine Threaded
\ Code (STC) is the most efficient method of running Forth upon it.
\
\ What you are reading is itself a Forth program, all the explanatory text is
\ are Forth comments. The file is fed through a preprocessor to turn it into
\ a [Markdown][] file for further processing.
\
\ Many Forths are written in an assembly language, especially the ones geared
\ towards microcontrollers, although it is more common for new Forth
\ interpreters to be written in C. A metacompiler is a [Cross Compiler][]
\ written in Forth.
\
\ ### References
\
\ * 'The Zen of eForth' by C. H. Ting
\ * <https://github.com/howerj/embed> (This project)
\ * <https://github.com/howerj/libforth>
\ * <https://github.com/howerj/forth-cpu>
\
\ #### Jones Forth:
\
\ * <https://rwmj.wordpress.com/2010/08/07/jonesforth-git-repository/>
\ * <https://github.com/AlexandreAbreu/jonesforth>
\
\ #### J1 CPU:
\
\ * <http://excamera.com/files/j1.pdf>
\ * <http://excamera.com/sphinx/fpga-j1.html>
\ * <https://github.com/jamesbowman/j1>
\ * <https://github.com/samawati/j1eforth>
\
\ #### Meta-compilation/Cross-Compilation:
\
\ * <http://www.ultratechnology.com/meta1.html>
\ * <https://en.wikipedia.org/wiki/Compiler-compiler#FORTH_metacompiler>
\
\ The Virtual Machine is specifically designed to execute Forth, it is a stack
\ machine that allows many Forth words to be encoded in one instruction but
\ does not contain any high level Forth words, just words like *@*, 'r>' and
\ a few basic words for I/O. A full description of the virtual machine is
\ in the appendix.
\
\ ## Metacompilation wordset
\
\ This section defines the metacompilation wordset as well as the
\ assembler. The metacompiler, or cross compiler, requires some assembly
\ instructions to be defined so the two word sets are interlinked.
\
\ A clear understanding of how Forth vocabularies work is needed before
\ proceeding with the tutorial. Vocabularies are the way Forth manages
\ namespaces and are generally talked about that much, they are especially
\ useful (in fact pretty much required) for writing a metacompiler.
.( FORTH META COMPILATION START ) cr
only forth definitions hex
system +order
variable meta ( Metacompilation vocabulary )
meta +order definitions
variable assembler.1 ( Target assembler vocabulary )
variable target.1 ( Target dictionary )
variable tcp ( Target dictionary pointer )
variable tlast ( Last defined word in target )
variable tdoVar ( Location of doVar in target )
variable tdoConst ( Location of doConst in target )
variable tdoNext ( Location of doNext in target )
variable tdoPrintString ( Location of .string in target )
variable tdoStringLit ( Location of string-literal in target )
variable fence ( Do not peephole optimize before this point )
1984 constant #version ( Version number )
5000 constant #target ( Location where target image will be built )
2000 constant #max ( Max number of cells in generated image )
2 constant =cell ( Target cell size )
-1 constant optimize ( Turn optimizations on [-1] or off [0] )
0 constant swap-endianess ( if true, swap the endianess )
$4100 constant pad-area ( area for pad storage )
$7FFF constant (rp0) ( start of return stack in *cells* )
$2400 constant (sp0) ( start of variable stack in *cells* )
variable header -1 header ! ( if true target headers generated )
( 1 constant verbose ( verbosity level, higher is more verbose )
#target #max 0 fill ( Erase the target memory location )
: ]asm assembler.1 +order ; immediate ( -- )
: a: current @ assembler.1 current ! : ; ( "name" -- wid link )
: a; [compile] ; current ! ; immediate ( wid link -- )
: ( [char] ) parse 2drop ; immediate ( "comment" -- discard until parenthesis )
: \ source drop @ >in ! ; immediate ( "comment" -- discard until end of line )
: there tcp @ ; ( -- a : target dictionary pointer value )
: tc! #target + c! ; ( u a -- : store character in target )
: tc@ #target + c@ ; ( a -- u : retrieve character in target )
: [last] tlast @ ; ( -- a : last defined word in target )
: low swap-endianess 0= if 1+ then ; ( b -- b : low byte at address )
: high swap-endianess if 1+ then ; ( b -- b : high byte at address)
: t! over $FF and over high tc! swap 8 rshift swap low tc! ; ( u a -- )
: t@ dup high tc@ swap low tc@ 8 lshift or ; ( a -- u )
: 2/ 1 rshift ; ( u -- u : non-standard definition divide by 2 )
: 2* 1 lshift ; ( u -- u : multiple by two, non-standard )
: talign there 1 and tcp +! ; ( -- : align target dictionary pointer value )
: tc, there tc! 1 tcp +! ; ( c -- : write byte into target dictionary )
: t, there t! =cell tcp +! ; ( u -- : write cell into target dictionary )
: tallot tcp +! ; ( n -- : allocate memory in target dictionary )
: update-fence there fence ! ; ( -- : update optimizer fence location )
: $literal ( <string>, -- )
[char] " word count dup tc, 1- for count tc, next drop talign update-fence ;
: tcells =cell * ; ( u -- a )
: tbody 1 tcells + ; ( a -- a )
: tcfa cfa ; ( PWD -- CFA )
: tnfa nfa ; ( PWD -- NFA )
: meta! ! ; ( u a -- )
: dump-hex #target there $10 + dump ; ( -- )
( : locations ( -- : list all words and locations in target dictionary )
( target.1 @ )
( begin )
( ?dup )
( while )
( dup )
( nfa count type space dup )
( cfa >body @ u. cr )
( $3FFF and @ )
( repeat ; )
: display ( -- : display metacompilation and target information )
( verbose 0= if exit then )
hex
." COMPILATION COMPLETE" cr
( verbose 1 u> if )
( dump-hex cr )
( ." TARGET DICTIONARY: " cr )
( locations )
( then )
." HOST: " here . cr
." TARGET: " there . cr
." HEADER: " #target $30 dump cr ;
$26 constant (header-options)
: checksum #target there crc ; ( -- u : calculate CRC of target image )
: save-hex ( -- : save target binary to file )
#target #target there + (save) throw ;
: finished ( -- : save target image and display statistics )
display
only forth definitions hex
." SAVING..." save-hex ." DONE" cr
." STACK>" .s cr ;
\ ### The Assembler
: [a] ( "name" -- : find word and compile an assembler word )
bl word assembler.1 search-wordlist 0= abort" [a]? "
cfa compile, ; immediate
: asm[ assembler.1 -order ; immediate ( -- )
\ There are five types of instructions, which are differentiated from each
\ other by the top bits of the instruction.
a: #literal $8000 a; ( literal instruction - top bit set )
a: #alu $6000 a; ( ALU instruction, further encoding below... )
a: #call $4000 a; ( function call instruction )
a: #?branch $2000 a; ( branch if zero instruction )
a: #branch $0000 a; ( unconditional branch )
\ An ALU instruction has a more complex encoding which can be seen in the table
\ in the appendix, it consists of a few flags for moving values to different
\ registers before and after the ALU operation to perform, an ALU operation,
\ and a return and variable stack increment/decrement.
\
\ Some of these operations are more complex than they first appear, either
\ because they do more than a single line explanation allows for, or because
\ they are not typical instructions that you would find in an actual processors
\ ALU and are only possible within the context of a virtual machine. Operations
\ like '#um/mod' are an example of the former, '#save' is an example of the
\ later.
\
\ The most succinct description of these operations, and the virtual machine,
\ is the source code for it which weighs in at under two hundred lines of
\ C code. Unfortunately this would not include that rationale that led to
\ the virtual machine being the way it is.
\
\ ALU Operations
a: #t $0000 a; ( T = t )
a: #n $0100 a; ( T = n )
a: #r $0200 a; ( T = Top of Return Stack )
a: #[t] $0300 a; ( T = memory[t] )
a: #n->[t] $0400 a; ( memory[t] = n )
a: #t+n $0500 a; ( n = n+t, T = carry )
a: #t*n $0600 a; ( n = n*t, T = upper bits of multiplication )
a: #t&n $0700 a; ( T = T and N )
a: #t|n $0800 a; ( T = T or N )
a: #t^n $0900 a; ( T = T xor N )
a: #~t $0A00 a; ( Invert T )
a: #t-1 $0B00 a; ( T == t - 1 )
a: #t==0 $0C00 a; ( T == 0? )
a: #t==n $0D00 a; ( T = n == t? )
a: #nu<t $0E00 a; ( T = n < t )
a: #n<t $0F00 a; ( T = n < t, signed version )
a: #n>>t $1000 a; ( T = n right shift by t places )
a: #n<<t $1100 a; ( T = n left shift by t places )
a: #sp@ $1200 a; ( T = variable stack depth )
a: #rp@ $1300 a; ( T = return stack depth )
a: #sp! $1400 a; ( set variable stack depth )
a: #rp! $1500 a; ( set return stack depth )
a: #save $1600 a; ( Save memory disk: n = start, T = end, T' = error )
a: #tx $1700 a; ( Transmit Byte: t = byte, T' = error )
a: #rx $1800 a; ( Block until byte received, T = byte/error )
a: #um/mod $1900 a; ( Remainder/Divide: Double Cell )
a: #/mod $1A00 a; ( Signed Remainder/Divide: Single Cell )
a: #bye $1B00 a; ( Exit Interpreter )
a: #vm $1C00 a; ( Arbitrary VM call )
a: #cpu $1D00 a; ( CPU information )
\ The Stack Delta Operations occur after the ALU operations have been executed.
\ They affect either the Return or the Variable Stack. An ALU instruction
\ without one of these operations (generally) do not affect the stacks.
a: d+1 $0001 or a; ( increment variable stack by one )
a: d-1 $0003 or a; ( decrement variable stack by one )
( a: d-2 $0002 or a; ( decrement variable stack by two, not used )
a: r+1 $0004 or a; ( increment variable stack by one )
a: r-1 $000C or a; ( decrement variable stack by one )
( a: r-2 $0008 or a; ( decrement variable stack by two, not used )
\ All of these instructions execute after the ALU and stack delta operations
\ have been performed except r->pc, which occurs before. They form part of
\ an ALU operation.
a: r->pc $0010 or a; ( Set Program Counter to Top of Return Stack )
a: n->t $0020 or a; ( Set Top of Variable Stack to Next on Variable Stack )
a: t->r $0040 or a; ( Set Top of Return Stack to Top on Variable Stack )
a: t->n $0080 or a; ( Set Next on Variable Stack to Top on Variable Stack )
\ There are five types of instructions; ALU operations, branches,
\ conditional branches, function calls and literals. ALU instructions
\ comprise of an ALU operation, stack effects and register move bits. Function
\ returns are part of the ALU operation instruction set.
: ?set dup $E000 and abort" argument too large " ; ( u -- )
a: branch 2/ ?set [a] #branch or t, a; ( a -- : an Unconditional branch )
a: ?branch 2/ ?set [a] #?branch or t, a; ( a -- : Conditional branch )
a: call 2/ ?set [a] #call or t, a; ( a -- : Function call )
a: ALU ?set [a] #alu or a; ( u -- : Make ALU instruction )
a: alu [a] ALU t, a; ( u -- : ALU operation )
a: literal ( n -- : compile a number into target )
dup [a] #literal and if ( numbers above $7FFF take up two instructions )
invert recurse ( the number is inverted, and 'literal' is called again )
[a] #~t [a] alu ( then an invert instruction is compiled into the target )
else
[a] #literal or t, ( numbers below $8000 are single instructions )
then a;
a: return ( -- : Compile a return into the target )
[a] #t [a] r->pc [a] r-1 [a] alu a;
\ ### Peep hole Optimizer
\
\ The following words implement a primitive peephole optimizer, which is not
\ the only optimization done, but is the major one. It performs tail call
\ optimizations and merges the return instruction with the previous instruction
\ if possible. These simple optimizations really make a lot of difference
\ in the size of meta-compiled program. It means proper tail recursive
\ procedures can be constructed.
\
\ The optimizer is wrapped up in the *exit,* word, it checks a fence variable
\ first, then the previously compiled cell to see if it can replace the last
\ compiled cell.
\
\ The fence variable is an address below which the peephole optimizer should
\ not look, this is to prevent the optimizer looking at data and merging with
\ it, or breaking control structures.
\
\ An exit can be merged into an ALU instruction if it does not contain
\ any return stack manipulation, or information from the return stack. This
\ includes operations such as *r->pc*, or *r+1*.
\
\ A call then an exit can be replaced with an unconditional branch to the
\ call.
\
\ If no optimization can be performed an *exit* instruction is written into
\ the target.
\
\ The optimizer can be held off manually be inserting a *nop*, which is a call
\ or instruction which does nothing, before the *exit*.
\
\ Other optimizations performed by the metacompiler, but not this optimizer,
\ include; inlining constant values and addresses, allowing the creation of
\ headerless words which are named only in the metacompiler and not in the
\ target, and the 'fallthrough;' word which allows for greater code sharing.
\ Some of these optimizations have a manual element to them, such as
\ 'fallthrough;'.
\
: previous there =cell - ; ( -- a )
: lookback previous t@ ; ( -- u )
: call? lookback $E000 and [a] #call = ; ( -- t )
: call>goto previous dup t@ $1FFF and swap t! ; ( -- )
: fence? fence @ previous u> ; ( -- t )
: safe? lookback $E000 and [a] #alu = lookback $001C and 0= and ; ( -- t )
: alu>return previous dup t@ [a] r->pc [a] r-1 swap t! ; ( -- )
: exit-optimize ( -- )
fence? if [a] return exit then
call? if call>goto exit then
safe? if alu>return exit then
[a] return ;
: exit, exit-optimize update-fence ; ( -- )
\ ### Meta-Compiler Defining Words and Control Structures
\
\ *compile-only* and *immediate* set bits in the latest defined word for
\ making a word a "compile only" word (one which can only be executed from
\ within a word definition) and "immediate" respectively. None of these
\ are relevant to the execution of the metacompiler so are not checked by it,
\ but are needed when the target Forth is up and running.
\
\ These words affect the target dictionaries word definitions and not the
\ meta-compilers definitions.
\
: compile-only tlast @ tnfa t@ $20 or tlast @ tnfa t! ; ( -- )
: immediate tlast @ tnfa t@ $40 or tlast @ tnfa t! ; ( -- )
\ *mcreate* creates a word in the metacompilers dictionary, not the targets.
\ For each word we create in the meta-compiled Forth we will need to create
\ at least one word in the meta-compilers dictionary which contains an address
\ of the Forth in the target.
\
: mcreate current @ >r target.1 current ! create r> current ! ;
\ *thead* compiles a word header into the target dictionary with a name
\ given a string. It is used by *t:*.
\
: thead ( b u -- : compile word header into target dictionary )
header @ 0= if 2drop exit then
talign
there [last] t, tlast !
there #target + pack$ c@ 1+ aligned tcp +! talign ;
\ *lookahead* parses the next word but leaves it in the input stream, pushing
\ a string to the parsed word. This is needed as we will be creating two
\ words with the same name with a word defined later on called *t:*, it
\ creates a word in the meta-compilers dictionary and compiles a word with
\ a header into the target dictionary.
\
: lookahead ( -- b u : parse a word, but leave it in the input stream )
>in @ >r bl parse r> >in ! ;
\ The word *h:* creates a headerless word in the target dictionary for
\ space saving reasons and to declutter the target search order. Ideally
\ it would instead add the word to a different vocabulary, so it is still
\ accessible to the programmer, but there is already very little room on the
\ target.
\
\ *h:* does not actually affect the target dictionary, it can be used by
\ itself and is called by *t:*. *h:* is used in conjunction with either
\ *fallthrough;* or *t;* (*t;* calls *fallthrough;*). *h:* but does several
\ things:
\
\ 1. It sets *<literal>* to the meta-compilers version of *literal* so that
\ when we are compiling words within a meta-compiled word definition it does
\ the right thing, which is compile a number literal into the target
\ dictionary.
\ 2. It pushes a magic number *$F00D* onto the stack, this popped off and
\ checked for by *fallthrough;*, if it is not present we have messed up a
\ word definition some how.
\ 3. It creates a meta-compiler word in *target.1*, this word-list consists
\ of pointers into the target word definitions. The created word when called
\ compiles a pointer to the word it represents into the target image.
\ 4. It updates the *fence* variable to hold off the optimizer.
\
\ *fallthrough;* allows words to be created which instead of exiting just
\ carry on into the next word, which is a space saving measure and provides
\ a minor speed boost. Due to the way this Forth stores word headers
\ *fallthrough;* cannot be used to fall through to a word defined with a
\ word header.
\
\ *t;* does everything *fallthrough;* does except it also compiles an exit
\ into the dictionary, which is how a normal word definition is terminated.
\
\ The words *[* and *]* could be said to change the state of the meta-compiler,
\ even though the meta-compiler is not effected by the *state* variable. A
\ possible improvement to the meta-compiler and to the eForth image it
\ generates would be to vector words like *quit* so the [Outer Interpreter][]
\ could be replaced by the meta-compiler, or at least words like *interpret*
\ could be vectored. Another improvement would be to load and unload the
\ meta-compilers vocabularies when the new definitions of *:* and *;* are
\ encountered. There are man possibilities, however the improvements given
\ by them would be minor, using the meta-compiler is mostly like writing normal
\ Forth.
\
: literal [a] literal ; ( u -- )
: [ ' literal <literal> ! ; ( -- )
: ] ' (literal) <literal> ! ; ( -- )
: h: ( -- : create a word with no name in the target dictionary )
[compile] [
$F00D mcreate there , update-fence does> @ [a] call ;
\ *t:* does everything *h:* does but also compiles a header for that word
\ into the dictionary using *thead*. It does affect the target dictionary
\ directly.
: t: ( "name", -- : creates a word in the target dictionary )
lookahead thead h: ;
: ?unstructured $F00D <> if source type cr 1 abort" unstructured! " then ;
\ @warning: Only use *fallthrough* to fall-through to words defined with *h:*.
: fallthrough; [compile] ] ?unstructured ; ( u -- )
: t; fallthrough; optimize if exit, else [a] return then ;
\ *;;* is used to do the same thing as 'fallthrough; h: <name>' in slightly
\ different way.
\
\ : a b h: cd c d ;; ( define 'a', a normal word, and 'cd' )
\ : cde cd e ; ( use 'cd', and also do 'e' )
\
\ This is still used for code sharing allowing the tail end of a word to be
\ used within a definition.
\
: ;; t; ?unstructured ;
\ *fetch-xt* is used to check that a variable contains a valid execution token,
\ to implement certain functionality we will need to refer to functions yet
\ to be defined in the target dictionary. We will not be able to use these
\ features until we have defined these functions. For example we cannot use
\ *tconstant*, which defines a constant in the target dictionary, until we
\ have defined the target versions of *doConst*. A reference to this function
\ will be stored in *tdoConst* once it has been defined in the target
\ dictionary.
: fetch-xt @ dup 0= abort" (null) " ; ( a -- xt )
\ *tconstant* as mentioned defines a constant in the target dictionary which
\ is visible in that target dictionary (that is, it has a header and when
\ *words* is run in the target it will be in that list).
\
\ *tconstant* behaves like *constant* does, it parses out a name and pops
\ a variable off of the stack. As mentioned, it cannot be used until *tdoConst*
\ has been filled with a reference to the targets *doConst*. *tconstant* makes
\ a word in the meta-compiler which points to a word it makes in the target.
\ This words purpose when run in the target is to push a constant onto the
\ stack. When the constant is referenced when compiling words with the
\ meta-compiler it does not compile references to the constant, but instead
\ it finds out what the constant was and compiles it in as a literal - which
\ is a small optimization.
: tconstant ( "name", n --, Run Time: -- )
>r
lookahead
thead
there tdoConst fetch-xt [a] call r> t, >r
mcreate r> ,
does> @ tbody t@ [a] literal ;
\ *tvariable* is like *tconstant* expect for variables. It requires *tdoVar*
\ is set to a reference to targets version of *doVar* which pushes a pointer
\ to the targets variable location when run in the target. It does a similar
\ optimization as *tconstant*, it does not actually compile a call to the
\ created variables *doVar* field but instead compiles the address as a literal
\ in the target when the word is called by the meta-compiler.
: tvariable ( "name", n -- , Run Time: -- a )
>r
lookahead
thead
there tdoVar fetch-xt [a] call r> t, >r
mcreate r> ,
does> @ tbody [a] literal ;
\ *tlocation* just reserves space in the target.
: tlocation ( "name", n -- : Reserve space in target for a memory location )
there swap t, mcreate , does> @ [a] literal ;
: [t] ( "name", -- a : get the address of a target word )
bl word target.1 search-wordlist 0= abort" [t]?"
cfa >body @ ;
: [f] ( "name", -- execute word in host Forth vocabulary )
bl word forth-wordlist search-wordlist 0= abort" [f]?"
cfa execute ;
\ @warning only use *[v]* on variables, not *tlocations*
: [v] [t] =cell + ; ( "name", -- a )
\ *xchange* takes two vocabularies defined in the target by their variable
\ names, "name1" and "name2", and updates "name1" so it contains the previously
\ defined words, and makes "name2" the vocabulary which subsequent definitions
\ are added to.
: xchange ( "name1", "name2", -- : exchange target vocabularies )
[last] [t] t! [t] t@ tlast meta! ;
\ These words implement the basic control structures needed to make
\ applications in the meta-compiled program, there are no immediate words
\ and they do not need to be, *t:* and *t;* do not change the interpreter
\ state, once the actual metacompilation begins everything is command mode.
\
\ 'postpone' is useful to make sure that a reference to the target definition
\ is definitely called.
\
: begin there update-fence ; ( -- a )
: until [a] ?branch ; ( a -- )
: if there update-fence 0 [a] ?branch ; ( -- a )
: skip there update-fence 0 [a] branch ; ( -- a )
: then begin 2/ over t@ or swap t! ; ( a -- )
: else skip swap then ; ( a -- a )
: while if swap ; ( a -- a a )
: repeat [a] branch then update-fence ; ( a -- )
: again [a] branch update-fence ; ( a -- )
: aft drop skip begin swap ; ( a -- a )
: constant mcreate , does> @ literal ; ( "name", a -- )
: [char] char literal ; ( "name" )
: postpone [t] [a] call ; ( "name", -- )
: next tdoNext fetch-xt [a] call t, update-fence ; ( a -- )
: exit exit, ; ( -- )
: ' [t] literal ; ( "name", -- )
: recurse tlast @ tcfa [a] call ; ( -- )
\ @bug maximum string length is 64 bytes, not 255 as it should be.
: ." tdoPrintString fetch-xt [a] call $literal ; ( "string", -- )
: $" tdoStringLit fetch-xt [a] call $literal ; ( "string", -- )
\ The following section adds the words implementable in assembly to the
\ metacompiler, when one of these words is used in the meta-compiled program
\ it will be implemented in assembly.
( ALU t->n t->r n->t rp sp NB. 'r->pc' in 'exit' )
: nop ]asm #t alu asm[ ;
: dup ]asm #t t->n d+1 alu asm[ ;
: over ]asm #n t->n d+1 alu asm[ ;
: invert ]asm #~t alu asm[ ;
: + ]asm #t+n n->t d-1 alu asm[ ;
: um+ ]asm #t+n alu asm[ ;
: * ]asm #t*n n->t d-1 alu asm[ ;
: um* ]asm #t*n alu asm[ ;
: swap ]asm #n t->n alu asm[ ;
: nip ]asm #t d-1 alu asm[ ;
: drop ]asm #n d-1 alu asm[ ;
: >r ]asm #n t->r r+1 d-1 alu asm[ ;
: r> ]asm #r t->n r-1 d+1 alu asm[ ;
: r@ ]asm #r t->n d+1 alu asm[ ;
: @ ]asm #[t] alu asm[ ;
: ! ]asm #n->[t] d-1 alu asm[ ;
: rshift ]asm #n>>t d-1 alu asm[ ;
: lshift ]asm #n<<t d-1 alu asm[ ;
: = ]asm #t==n d-1 alu asm[ ;
: u< ]asm #nu<t d-1 alu asm[ ;
: < ]asm #n<t d-1 alu asm[ ;
: and ]asm #t&n d-1 alu asm[ ;
: xor ]asm #t^n d-1 alu asm[ ;
: or ]asm #t|n d-1 alu asm[ ;
: sp@ ]asm #sp@ t->n d+1 alu asm[ ;
: sp! ]asm #sp! alu asm[ ;
: 1- ]asm #t-1 alu asm[ ;
: rp@ ]asm #rp@ t->n d+1 alu asm[ ;
: rp! ]asm #rp! d-1 alu asm[ ;
: 0= ]asm #t==0 alu asm[ ;
: yield? ]asm #bye alu asm[ ;
: rx? ]asm #rx t->n n->t d+1 alu asm[ ;
: tx! ]asm #tx n->t d-1 alu asm[ ;
: (save) ]asm #save d-1 alu asm[ ;
: um/mod ]asm #um/mod t->n alu asm[ ;
: /mod ]asm #/mod t->n alu asm[ ;
: / ]asm #/mod d-1 alu asm[ ;
: mod ]asm #/mod n->t d-1 alu asm[ ;
: vm ]asm #vm alu asm[ ;
: cpu-xchg ]asm #cpu alu asm[ ;
: cpu! ]asm #cpu n->t d-1 alu asm[ ;
: rdrop ]asm #t r-1 alu asm[ ;
\ Some words can be implemented in a single instruction which have no
\ analogue within Forth.
: dup@ ]asm #[t] t->n d+1 alu asm[ ;
: dup0= ]asm #t==0 t->n d+1 alu asm[ ;
: dup>r ]asm #t t->r r+1 alu asm[ ;
: 2dup= ]asm #t==n t->n d+1 alu asm[ ;
: 2dupxor ]asm #t^n t->n d+1 alu asm[ ;
: 2dup< ]asm #n<t t->n d+1 alu asm[ ;
: rxchg ]asm #r t->r alu asm[ ;
: over-and ]asm #t&n alu asm[ ;
: over-xor ]asm #t^n alu asm[ ;
\ *for* needs the new definition of *>r* to work correctly.
: for >r begin ;
: meta: : ;
( : :noname h: ; )
: : t: ;
meta: ; t; ;
hide meta:
hide t:
hide t;
]asm #~t ALU asm[ constant =invert ( invert instruction )
]asm #t r->pc r-1 ALU asm[ constant =exit ( return/exit instruction )
]asm #n t->r d-1 r+1 ALU asm[ constant =>r ( to r. stk. instruction )
$20 constant =bl ( blank, or space )
$D constant =cr ( carriage return )
$A constant =lf ( line feed )
$8 constant =bs ( back space )
$7F constant =del ( delete key )
$1B constant =escape ( escape character )
$10 constant dump-width ( number of columns for *dump* )
$50 constant tib-length ( size of terminal input buffer )
$40 constant word-length ( maximum length of a word )
$40 constant c/l ( characters per line in a block )
$10 constant l/b ( lines in a block )
$F constant l/b-1 ( lines in a block, less one )
(rp0) 2* constant rp0 ( start of return stack )
(sp0) 2* constant sp0 ( start of variable stack )
$2BAD constant magic ( magic number for compiler security )
$F constant #highest ( highest bit in cell )
\ @todo Move <key>, <emit> to system vocabulary
( Volatile variables )
\ $4000 Unused
$4002 constant last-def ( last, possibly unlinked, word definition )
$4006 constant id ( used for source id )
$4008 constant seed ( seed used for the PRNG )
$400A constant handler ( current handler for throw/catch )
$400C constant block-dirty ( -1 if loaded block buffer is modified )
$4010 constant <key> ( -- c : new character, blocking input )
$4012 constant <emit> ( c -- : emit character )
$4014 constant <expect> ( "accept" vector )
$4016 constant <tap> ( "tap" vector, for terminal handling )
$4018 constant <echo> ( c -- : emit character )
$401A constant context ( holds current context for search order )
( area for context is #vocs large )
$402A constant #tib ( Current count of terminal input buffer )
$402C constant tib-buf ( ... and address )
$402E constant tib-start ( backup tib-buf value )
\ $4100 == pad-area
\ $C constant vm-options ( Virtual machine options register )
$1E constant header-length ( location of length in header )
$20 constant header-crc ( location of CRC in header )
(header-options) constant header-options ( location of options bits in header )
target.1 +order ( Add target word dictionary to search order )
meta -order meta +order ( Reorder so *meta* has a higher priority )
system -order ( Remove system vocabulary to previously accidents )
forth-wordlist -order ( Remove normal Forth words to prevent accidents )
\ # The Target Forth
\ With the assembler and meta compiler complete, we can now make our target
\ application, a Forth interpreter which will be able to read in this file
\ and create new, possibly modified, images for the Forth virtual machine
\ to run.
\
\ ## The Image Header
\
\ The following *t,* sequence reserves space and partially populates the
\ image header with file format information, based upon the PNG specification.
\ See <http://www.fadden.com/tech/file-formats.html> and
\ <https://stackoverflow.com/questions/323604> for more information about
\ how to design binary formats.
\
\ The header contains enough information to identify the format, the
\ version of the format, and to detect corruption of data, as well as
\ having a few other nice properties - some having to do with how other
\ systems and programs may deal with the binary (such as have a string literal
\ *FTH* to help identify the binary format, and the first byte being outside
\ the ASCII range of characters so it is obvious that the file is meant to
\ be treated as a binary and not as text).
\
0 t, \ $0: PC: program counter, jump to start / reset vector
0 t, \ $2: T, top of stack
(rp0) t, \ $4: RP0, return stack pointer
(sp0) t, \ $6: SP0, variable stack pointer
0 t, \ $8: Instruction exception vector
$8000 t, \ $A: VM Memory Size in cells
$0000 t, \ $C: VM Options
0 t, \ $E: Shadow PC
0 t, \ $10: Shadow T
(rp0) t, \ $12: Shadow RP0
(sp0) t, \ $14: Shadow SP0
$4689 t, \ $16: 0x89 'F'
$4854 t, \ $18: 'T' 'H'
$0A0D t, \ $1A: '\r' '\n'
$0A1A t, \ $1C: ^Z '\n'
0 t, \ $1E: For Length of Forth image, different from VM size
0 t, \ $20: For CRC of Forth image, not entire VM memory
$0001 t, \ $22: Endianess check
#version t, \ $24: Version information
$0001 t, \ $26: Header options
\ ## First Word Definitions
\ The very first definitions are for the first stage boot loader, which can
\ be used to manipulate the image before we do anything else
\
0 tlocation <cold> ( location of 'cold' )
[t] <cold> 2/ 0 t! ( set starting word )
[t] <cold> 2/ $E t! ( set shadow register starting location )
\ After the header and boot-loader words, two short words are defined,
\ visible only to the meta compiler and used by its internal machinery. The
\ words are needed by *tvariable* and *tconstant*, and these constructs cannot
\ be used without them. This is an example of the metacompiler and the
\ meta-compiled program being intermingled, which should be kept to a minimum.
h: doVar r> ; ( -- a : push return address and exit to caller )
h: doConst r> @ ; ( -- u : push value at return address and exit to caller )
\ Here the address of *doVar* and *doConst* in the target is stored in
\ variables accessible by the metacompiler, so *tconstant* and *tvariable* can
\ compile references to them in the target.
[t] doVar tdoVar meta!
[t] doConst tdoConst meta!
\ Next some space is reserved for variables which will have no name in the
\ target and are not on the target Forths search order. We do this with
\ *tlocation*. These variables are needed for the internal working of the
\ interpreter but the application programmer using the target Forth can make
\ do without them, so they do not have names within the target.
\
\ A short description of the variables and their uses:
\
\ *cp* is the dictionary pointer, which usually is only incremented in order
\ to reserve space in this dictionary. Words like *,* and *:* advance this
\ variable.
\
\ *root-voc*, *editor-voc*, *assembler-voc*, and *_forth-wordlist* are
\ variables which point to word lists, they can be used with *set-order*
\ and pointers to them may be returned by *get-order*. By default the only
\ vocabularies loaded are the root vocabulary (which contains only a few
\ vocabulary manipulation words) and the forth vocabulary are loaded (which
\ contains most of the words in a standard Forth).
\
\ None of these variables are set to any meaningful values here and will be
\ updated during the metacompilation process.
\
0 tlocation root-voc ( root vocabulary )
0 tlocation editor-voc ( editor vocabulary )
\ System Variables
#version constant ver ( eForth version )
pad-area tconstant pad ( pad variable - offset into temporary storage )
$8 constant #vocs ( number of vocabularies in allowed )
$2 tconstant cell ( size of a cell in bytes )
$400 tconstant b/buf ( size of a block )
0 tlocation cp ( Dictionary Pointer: Set at end of file )
0 tlocation _forth-wordlist ( set at the end near the end of the file )
0 tlocation _system ( system specific vocabulary )
$0 tvariable >in ( Hold character pointer when parsing input )
$0 tvariable state ( compiler state variable )
$0 tvariable hld ( Pointer into hold area for numeric output )
$A tvariable base ( Current output radix )
$0 tvariable span ( Hold character count received by expect )
0 tvariable blk ( current blk loaded, set in *cold* )
$FFFF tvariable dpl ( number of places after fraction )
0 tvariable current ( WID to add definitions to )
xchange _forth-wordlist _system
0 tvariable <literal> ( holds execution vector for literal )
0 tvariable <boot> ( execute program at startup )
0 tvariable <ok> ( prompt execution vector )
xchange _system _forth-wordlist
\
\ ## Target Assembly Words
\
\ The first words added to the target Forths dictionary are all based on
\ assembly instructions. The definitions may seem like nonsense, how does the
\ definition of *+* work? It appears that the definition calls itself, which
\ obviously would not work. The answer is in the order new words are added
\ into the dictionary. In Forth, a word definition is not placed in the
\ search order until the definition of that word is complete, this allows
\ the previous definition of a word to be use within that definition, and
\ requires a separate word (*recurse*) to implement recursion.
\
\ However, the words *:* and *;* are not the normal Forth define and end
\ definitions words, they are the meta-compilers and they behave differently,
\ *:* is implemented with *t:* and *;* with *t;*.
\
\ *t:* uses *create* to make a new variable in the meta-compilers
\ dictionary that points to a word definition in the target, it also creates
\ the words header in the target (*h:* is the same, but without a header
\ being made in the target). The word is compilable into the target as soon
\ as it is defined, yet the definition of *+* is not recursive because the
\ meta-compilers search order, *meta*, is higher that the search order for
\ the words containing the meta-compiled target addresses, *target.1*, so the
\ assembly for *+* gets compiled into the definition of *+*.
\
\ Manipulation of the word search order is key in understanding how the
\ metacompiler works.
\
\ The following words will be part of the main search order, in
\ *forth-wordlist* and in the assembly search order.
\
( : nop nop ; ( -- : do nothing )
: dup dup ; ( n -- n n : duplicate value on top of stack )
: over over ; ( n1 n2 -- n1 n2 n1 : duplicate second value on stack )
: invert invert ; ( u -- u : bitwise invert of value on top of stack )
xchange _forth-wordlist _system
: um+ um+ ; ( u u -- u carry : addition with carry )
: um* um* ; ( u u -- ud : multiplication )
xchange _system _forth-wordlist
: + + ; ( u u -- u : addition without carry )
: * * ; ( u u -- u : multiplication )
: swap swap ; ( n1 n2 -- n2 n1 : swap two values on stack )
: nip nip ; ( n1 n2 -- n2 : remove second item on stack )
: drop drop ; ( n -- : remove item on stack )
: @ @ ; ( a -- u : load value at address )
: ! ! ; ( u a -- : store *u* at address *a* )
: rshift rshift ; ( u1 u2 -- u : shift u2 by u1 places to the right )
: lshift lshift ; ( u1 u2 -- u : shift u2 by u1 places to the left )
: = = ; ( u1 u2 -- t : does u2 equal u1? )
: u< u< ; ( u1 u2 -- t : is u2 less than u1 )
: < < ; ( u1 u2 -- t : is u2 less than u1, signed version )
: and and ; ( u u -- u : bitwise and )
: xor xor ; ( u u -- u : bitwise exclusive or )
: or or ; ( u u -- u : bitwise or )
( : sp@ sp@ ; ( ??? -- u : get stack depth )
( : sp! sp! ; ( u -- ??? : set stack depth )
: 1- 1- ; ( u -- u : decrement top of stack )
: 0= 0= ; ( u -- t : if top of stack equal to zero )
( h: yield? yield? ; ( u -- !!! : exit VM with *u* as return value )
xchange _forth-wordlist _system
: rx? rx? ; ( -- c t | -1 t : fetch a single character, or EOF )
: tx! tx! ; ( c -- : transmit single character )
: (save) (save) ; ( u1 u2 -- u : save memory from u1 to u2 inclusive )
: vm vm ; ( ??? -- ??? : perform arbitrary VM call )
xchange _system _forth-wordlist
: um/mod um/mod ; ( d u2 -- rem div : mixed unsigned divide/modulo )
: /mod /mod ; ( u1 u2 -- rem div : signed divide/modulo )
: / / ; ( u1 u2 -- u : u1 divided by u2 )
: mod mod ; ( u1 u2 -- u : remainder of u1 divided by u2 )
( h: cpu! cpu! ; ( u -- : set CPU options register )
\
\ ### Forth Implementation of Arithmetic Functions
\
\ As an aside, the basic arithmetic functions of Forth can be implemented
\ in terms of simple addition, some tests and bit manipulation, if they
\ are not available for your system. Division, the remainder operation and
\ multiplication are provided by the virtual machine in this case, but it
\ is interesting to see how these words are put together. The first task it
\ to implement an add with carry, or *um+*. Once this is available, *um/mod*
\ and *um\** are coded.
\
\ : um+ ( w w -- w carry )
\ over over + >r
\ r@ 0 < invert >r
\ over over and
\ 0 < r> or >r
\ or 0 < r> and invert 1 +
\ r> swap ;
\
\ $F constant #highest
\ : um/mod ( ud u -- r q )
\ ?dup 0= if $A -throw exit then
\ 2dup u<
\ if negate #highest
\ for >r dup um+ >r >r dup um+ r> + dup
\ r> r@ swap >r um+ r> or
\ if >r drop 1+ r> else drop then r>
\ next
\ drop swap exit
\ then drop 2drop [-1] dup ;
\
\ : m/mod ( d n -- r q ) \ floored division
\ dup 0< dup>r
\ if
\ negate >r dnegate r>
\ then
\ >r dup 0< if r@ + then r> um/mod r>
\ if swap negate swap exit then ;
\
\ : um* ( u u -- ud )
\ 0 swap ( u1 0 u2 ) #highest
\ for dup um+ >r >r dup um+ r> + r>
\ if >r over um+ r> + then
\ next rot-drop ;
\
\ The other arithmetic operations follow from the previous definitions almost
\ trivially:
\
\ : /mod over 0< swap m/mod ; ( n n -- r q )
\ : mod /mod drop ; ( n n -- r )
\ : / /mod nip ; ( n n -- q )
\ : * um* drop ; ( n n -- n )
\ : m* 2dup xor 0< >r abs swap abs um* r> if dnegate then ; ( n n -- d )