Repository navigation
Expand file tree
/
Copy pathforth.asm
More file actions
1448 lines (1298 loc) · 30 KB
/
Copy pathforth.asm
File metadata and controls
1448 lines (1298 loc) · 30 KB
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
; ---------------------------------------------------------------------------
; forth.asm -- a small Forth interpreter for Linux/amd64, pure NASM assembly.
;
; Based on a classic indirect-threaded model.
;
; registers:
; rsi = Forth instruction pointer (points into a thread: a list of "xt"s)
; rbp = data (parameter) stack pointer, grows down
; rsp = the native CPU stack, reused as the Forth return stack
;
; A dictionary entry ("word") looks like:
; dq link -> previous dictionary entry (0 = end of list)
; db immediate -> 0 or 1
; db namelen
; db name[namelen]
; dq codeword -> address to jump to when this word runs
; ... parameter field (only for colon definitions / variables)
;
; An "xt" (execution token) is the address of a word's codeword field.
; NEXT fetches the next xt from the current thread, then jumps to the
; address stored there:
;
; NEXT: lodsq ; rax = xt = [rsi], rsi += 8
; jmp [rax] ; jump to *xt (rax itself is left = xt)
;
; For primitives (defcode) the codeword points directly at the hand-written
; machine code that implements the word, which ends by running NEXT again.
; For colon definitions the codeword is DOCOL, which pushes the caller's
; rsi (return address) on the return stack and starts executing the new
; word's own thread -- EXIT pops rsi back off to resume the caller.
;
; Build:
; nasm -f elf64 forth.asm -o forth.o
; ld -o forth forth.o
;
; Run:
; echo ": square dup * ; 5 square ." | ./forth
; ---------------------------------------------------------------------------
BITS 64
%define RETURN_STACK_CELLS 8192
%define LOOP_STACK_FRAMES 4096
%macro push_ds 1
sub rbp, 8
cmp rbp, data_stack
jae %%ok
jmp data_stack_overflow_error
%%ok:
mov [rbp], %1
%endmacro
%macro pop_ds 1
cmp rbp, data_stack_top
jb %%ok
jmp data_stack_underflow_error
%%ok:
mov %1, [rbp]
add rbp, 8
%endmacro
; Guards words that peek at the stack (DUP, OVER, 2DUP, ...) without going
; through pop_ds -- verifies at least %1 items are present before reading.
%macro check_ds_depth 1
lea rax, [rbp+(%1-1)*8]
cmp rax, data_stack_top
jb %%ok
jmp data_stack_underflow_error
%%ok:
%endmacro
; Guards compile-only words (IF, DO, LOOP, ...): they work by *compiling*
; helper code into the dictionary rather than doing anything themselves, so
; using them outside a colon definition would silently corrupt HERE and
; leave garbage for words like I to read. Bail out to a clean error instead.
%macro check_compiling 0
mov rax, [var_STATE]
test rax, rax
jz compile_only_error
%endmacro
%macro NEXT 0
lodsq
jmp [rax]
%endmacro
; Dictionary-building macro. Chains every word together at assemble time
; via the text macro LINK, which always holds the label of the previously
; defined word (or the literal 0 for the very first one).
%define LINK 0
%macro defcode 3
section .data
align 8
name_%1:
dq LINK
%define LINK name_%1
db %3
%strlen %%L %2
db %%L
db %2
name_%1_CFA:
dq code_%1
section .text
code_%1:
%endmacro
; Entry point
section .text
global _start
_start:
cld
mov rbp, data_stack_top
; Use our own return stack instead of the one the OS gave this process,
; pre-filled everywhere with QUIT_thread. rsp normally holds real return
; addresses pushed by DOCOL/(DO), but a stray EXIT (popping deeper than
; anything legitimately pushed) will pop one of these sentinels instead
; of garbage, harmlessly resuming the interpreter rather than crashing.
; rsp starts in the middle of the buffer so there is headroom both for
; genuine call/loop nesting (below) and for repeated stray pops (above).
mov rdi, return_stack
mov rax, QUIT_thread
mov rcx, RETURN_STACK_CELLS
rep stosq
mov rsp, return_stack + (RETURN_STACK_CELLS/2)*8
mov rsi, QUIT_thread
NEXT
; DOCOL: codeword for every colon definition. rax = xt of the word being
; entered (NEXT leaves it there); the parameter field starts right after
; the codeword, i.e. at rax+8.
DOCOL:
cmp rsp, return_stack + 128
ja .ok
jmp return_stack_overflow_error
.ok:
push rsi
lea rsi, [rax+8]
NEXT
; DOVAR: codeword for VARIABLE words -- just pushes the address of the
; single storage cell that follows the codeword.
DOVAR:
lea rbx, [rax+8]
push_ds rbx
NEXT
; DOCON: codeword for CONSTANT words -- pushes the value stored in the
; single cell that follows the codeword (not its address, unlike DOVAR).
DOCON:
mov rbx, [rax+8]
push_ds rbx
NEXT
; Core VM primitives: EXIT, LIT, BRANCH, 0BRANCH
defcode EXIT, "EXIT", 0
pop rsi
NEXT
defcode LIT, "LIT", 0
lodsq
push_ds rax
NEXT
defcode BRANCH, "BRANCH", 0
mov rsi, [rsi]
NEXT
defcode ZBRANCH, "0BRANCH", 0
pop_ds rax
test rax, rax
jnz .no_branch
mov rsi, [rsi]
NEXT
.no_branch:
add rsi, 8
NEXT
; Data stack primitives
defcode DUP, "DUP", 0
check_ds_depth 1
mov rax, [rbp]
push_ds rax
NEXT
defcode DROP, "DROP", 0
pop_ds rax
NEXT
defcode SWAP, "SWAP", 0
pop_ds rax
pop_ds rbx
push_ds rax
push_ds rbx
NEXT
defcode OVER, "OVER", 0
check_ds_depth 2
mov rax, [rbp+8]
push_ds rax
NEXT
defcode ROT, "ROT", 0
pop_ds rax ; c
pop_ds rbx ; b
pop_ds rcx ; a
push_ds rbx ; b
push_ds rax ; c
push_ds rcx ; a
NEXT
defcode DOTS, ".S", 0
mov r12, rbp
.loop:
cmp r12, data_stack_top
jae .done
mov rax, [r12]
call print_num
call print_space
add r12, 8
jmp .loop
.done:
NEXT
defcode QDUP, "?DUP", 0
check_ds_depth 1
mov rax, [rbp]
test rax, rax
jz .done
push_ds rax
.done:
NEXT
defcode DEPTH, "DEPTH", 0
mov rax, data_stack_top
sub rax, rbp
shr rax, 3
push_ds rax
NEXT
; ( xu ... x1 x0 u -- xu ... x1 x0 xu ) -- u=0 picks the current top (same
; as DUP), u=1 the item below it, and so on.
defcode PICK, "PICK", 0
pop_ds rax ; u
lea rbx, [rbp+rax*8]
cmp rbx, data_stack_top
jb .ok
jmp data_stack_underflow_error
.ok:
mov rax, [rbx]
push_ds rax
NEXT
; Arithmetic
defcode ADD, "+", 0
pop_ds rax
pop_ds rbx
add rax, rbx
push_ds rax
NEXT
defcode SUB, "-", 0
pop_ds rax ; b
pop_ds rbx ; a
sub rbx, rax
push_ds rbx
NEXT
defcode MUL, "*", 0
pop_ds rax
pop_ds rbx
imul rax, rbx
push_ds rax
NEXT
defcode DIV, "/", 0
pop_ds rax ; divisor
pop_ds rbx ; dividend
mov r8, rax
mov rax, rbx
cqo
idiv r8
push_ds rax
NEXT
defcode MOD, "MOD", 0
pop_ds rax ; divisor
pop_ds rbx ; dividend
mov r8, rax
mov rax, rbx
cqo
idiv r8
push_ds rdx
NEXT
; Comparisons (true = -1, false = 0) and bitwise logic
defcode EQ, "=", 0
pop_ds rax
pop_ds rbx
cmp rbx, rax
mov rax, 0
jne .no
mov rax, -1
.no:
push_ds rax
NEXT
defcode LT, "<", 0
pop_ds rax
pop_ds rbx
cmp rbx, rax
mov rax, 0
jge .no
mov rax, -1
.no:
push_ds rax
NEXT
defcode GT, ">", 0
pop_ds rax
pop_ds rbx
cmp rbx, rax
mov rax, 0
jle .no
mov rax, -1
.no:
push_ds rax
NEXT
defcode ZEQ, "0=", 0
pop_ds rax
cmp rax, 0
mov rax, 0
jne .no
mov rax, -1
.no:
push_ds rax
NEXT
defcode ZLT, "0<", 0
pop_ds rax
cmp rax, 0
mov rax, 0
jge .no
mov rax, -1
.no:
push_ds rax
NEXT
defcode LAND, "AND", 0
pop_ds rax
pop_ds rbx
and rax, rbx
push_ds rax
NEXT
defcode LOR, "OR", 0
pop_ds rax
pop_ds rbx
or rax, rbx
push_ds rax
NEXT
defcode LINVERT, "INVERT", 0
pop_ds rax
not rax
push_ds rax
NEXT
; Memory
defcode STORE, "!", 0
pop_ds rax ; addr
pop_ds rbx ; value
mov [rax], rbx
NEXT
defcode FETCH, "@", 0
pop_ds rax ; addr
mov rbx, [rax]
push_ds rbx
NEXT
; I/O
defcode DOT, ".", 0
pop_ds rax
call print_num
call print_space
NEXT
defcode EMIT, "EMIT", 0
pop_ds rax
mov [charbuf], al
push rsi
mov rax, 1
mov rdi, 1
lea rsi, [charbuf]
mov rdx, 1
syscall
pop rsi
NEXT
defcode CR, "CR", 0
mov byte [charbuf], 10
push rsi
mov rax, 1
mov rdi, 1
lea rsi, [charbuf]
mov rdx, 1
syscall
pop rsi
NEXT
defcode KEY, "KEY", 0
call read_char
push_ds rax
NEXT
defcode BYE, "BYE", 0
mov rax, 60
xor rdi, rdi
syscall
; Compiling words: : ; IMMEDIATE VARIABLE
defcode COLON, ":", 0
call word_
test rax, rax
jz .done ; EOF right after ':' -- nothing to do
mov r8, [var_HERE]
mov r9, [var_LATEST]
mov [var_SAVED_LATEST], r9 ; so an error mid-definition can unlink this word
mov [r8], r9
mov byte [r8+8], 0
mov [r8+9], cl
lea r10, [r8+10]
xor r11, r11
.copy:
cmp r11, rcx
jge .nameend
mov al, [rdi+r11]
mov [r10+r11], al
inc r11
jmp .copy
.nameend:
lea r10, [r10+rcx] ; codeword field address
mov qword [r10], DOCOL
lea r11, [r10+8] ; start of parameter field
mov [var_LATEST], r8
mov [var_HERE], r11
mov qword [var_STATE], 1
.done:
NEXT
defcode SEMICOLON, ";", 1
check_compiling
mov r11, [var_HERE]
mov qword [r11], name_EXIT_CFA
add r11, 8
mov [var_HERE], r11
mov qword [var_STATE], 0
NEXT
; IMMEDIATE must itself be immediate: the standard idiom is to write it
; *inside* a definition, right after the name (": foo IMMEDIATE ... ;"),
; so it needs to run the moment it's encountered while compiling -- to
; flag the word currently being built -- rather than get compiled into
; that word's body and mis-flag whatever LATEST happens to be much later,
; at run time.
defcode IMMEDIATE, "IMMEDIATE", 1
mov r8, [var_LATEST]
mov byte [r8+8], 1
NEXT
defcode VARIABLE, "VARIABLE", 0
call word_
test rax, rax
jz .done
mov r8, [var_HERE]
mov r9, [var_LATEST]
mov [r8], r9
mov byte [r8+8], 0
mov [r8+9], cl
lea r10, [r8+10]
xor r11, r11
.copy:
cmp r11, rcx
jge .nameend
mov al, [rdi+r11]
mov [r10+r11], al
inc r11
jmp .copy
.nameend:
lea r10, [r10+rcx]
mov qword [r10], DOVAR
lea r14, [r10+8] ; the variable's one storage cell
mov qword [r14], 0
mov [var_LATEST], r8
lea r15, [r14+8]
mov [var_HERE], r15
.done:
NEXT
defcode WORDS, "WORDS", 0
; r8 = current dictionary entry
mov r8, [var_LATEST]
.loop:
test r8, r8
jz .done
; Dictionary layout:
; [r8+0] = link
; [r8+8] = immediate flag
; [r8+9] = name length
; [r8+10] = name
movzx r9d, byte [r8+9]
lea r10, [r8+10]
; write(1, name, length)
push rsi
mov rax, 1
mov rdi, 1
mov rsi, r10
mov rdx, r9
syscall
pop rsi
; Space after the word name.
call print_space
; Follow the dictionary link.
mov r8, [r8]
jmp .loop
.done:
; Finish with a newline.
push rsi
mov byte [charbuf], 10
mov rax, 1
mov rdi, 1
lea rsi, [charbuf]
mov rdx, 1
syscall
pop rsi
NEXT
; Control structures. These are IMMEDIATE: the interpreter always runs them
; right away (even while compiling), so they execute natively during
; compilation and use the data stack to remember addresses that need
; back-patching -- the standard technique for threaded Forths.
defcode IF, "IF", 1
check_compiling
mov r11, [var_HERE]
mov qword [r11], name_ZBRANCH_CFA
add r11, 8
push_ds r11 ; remember placeholder cell address
add r11, 8
mov [var_HERE], r11
NEXT
defcode ELSE, "ELSE", 1
check_compiling
mov r11, [var_HERE]
mov qword [r11], name_BRANCH_CFA
lea r12, [r11+8] ; ELSE's own placeholder cell
lea r13, [r11+16] ; start of the else-part
mov [var_HERE], r13
pop_ds rax ; IF's placeholder cell address
mov [rax], r13
push_ds r12
NEXT
defcode THEN, "THEN", 1
check_compiling
pop_ds rax
mov r10, [var_HERE]
mov [rax], r10
NEXT
defcode BEGIN, "BEGIN", 1
check_compiling
mov r10, [var_HERE]
push_ds r10
NEXT
defcode UNTIL, "UNTIL", 1
check_compiling
mov r11, [var_HERE]
mov qword [r11], name_ZBRANCH_CFA
add r11, 8
pop_ds rax
mov [r11], rax
add r11, 8
mov [var_HERE], r11
NEXT
defcode DO, "DO", 1
check_compiling
mov r11, [var_HERE]
mov qword [r11], name_PAREN_DO_CFA
add r11, 8
mov [var_HERE], r11
push_ds r11 ; loop-start address
NEXT
defcode LOOP, "LOOP", 1
check_compiling
mov r11, [var_HERE]
mov qword [r11], name_PAREN_LOOP_CFA
add r11, 8
mov qword [r11], name_ZBRANCH_CFA
add r11, 8
pop_ds rax ; loop-start address (from DO)
mov [r11], rax
add r11, 8
mov [var_HERE], r11
NEXT
; (DO) and (LOOP) push the loop limit/index onto their own dedicated loop-
; control stack (loop_stack/var_LOOP_SP) rather than the call/return stack.
; Keeping it separate from rsp -- which DOCOL/EXIT touch on every call --
; means a helper word invoked from inside the loop body doesn't disturb it:
; I and J read whichever loop is dynamically innermost/next-outer at the
; moment they run, regardless of how many word calls deep they're called
; from. Each frame is 2 cells: [rcx] = index, [rcx+8] = limit.
defcode PAREN_DO, "(DO)", 0
pop_ds rax ; index
pop_ds rbx ; limit
mov rcx, [var_LOOP_SP]
sub rcx, 16
cmp rcx, loop_stack
jae .ok
jmp loop_stack_overflow_error
.ok:
mov [rcx], rax
mov [rcx+8], rbx
mov [var_LOOP_SP], rcx
NEXT
defcode PAREN_LOOP, "(LOOP)", 0
mov rcx, [var_LOOP_SP]
mov rax, [rcx] ; index
mov rbx, [rcx+8] ; limit
inc rax
cmp rax, rbx
jge .finished
mov [rcx], rax
xor rax, rax
push_ds rax ; not finished -> 0BRANCH branches back
NEXT
.finished:
add qword [var_LOOP_SP], 16
mov rax, 1
push_ds rax ; finished -> 0BRANCH falls through
NEXT
defcode I, "I", 0
mov rax, [var_LOOP_SP]
cmp rax, loop_stack_top
jb .ok
jmp loop_stack_underflow_error
.ok:
mov rax, [rax]
push_ds rax
NEXT
; J reads the next loop out from I -- the enclosing loop's index when used
; inside a nested DO...LOOP (directly, or through any number of calls).
defcode J, "J", 0
mov rax, [var_LOOP_SP]
add rax, 16
cmp rax, loop_stack_top
jb .ok
jmp loop_stack_underflow_error
.ok:
mov rax, [rax]
push_ds rax
NEXT
; Explicit return-stack access, for stashing values across calls that would
; otherwise be in the data stack's way. Shares the same physical stack as
; DOCOL/EXIT (DO/LOOP's index/limit pairs live on their own loop_stack, see
; below), so it's on the caller to keep >R/R> balanced within a definition
; -- an unbalanced >R left on the stack across EXIT will confuse whatever
; the caller does with its own return address next.
defcode TOR, ">R", 0
cmp rsp, return_stack + 128
ja .ok
jmp return_stack_overflow_error
.ok:
pop_ds rax
push rax
NEXT
defcode FROMR, "R>", 0
pop rax
push_ds rax
NEXT
defcode RFETCH, "R@", 0
mov rax, [rsp]
push_ds rax
NEXT
; String literals. ." and S" are IMMEDIATE: the text between them and the
; closing '"' is read directly off the input stream (not tokenized by
; word_, since it may contain spaces), right when the word is encountered
; -- whether interpreting or compiling. While interpreting, the text is
; just used right away; while compiling, it is embedded straight into the
; definition's thread (a length cell followed by the raw bytes) after a
; runtime word that knows how to skip back over its own inline data.
defcode DOTQUOTE, '."', 1
call read_string_lit ; rcx = length, bytes copied into strbuf
mov r10, [var_STATE]
test r10, r10
jnz .compile
push rsi
mov rdx, rcx
mov rax, 1
mov rdi, 1
lea rsi, [strbuf]
syscall
pop rsi
NEXT
.compile:
mov r11, [var_HERE]
mov qword [r11], name_PAREN_DOTQUOTE_CFA
add r11, 8
mov [r11], rcx
add r11, 8
xor r9, r9
.copyloop:
cmp r9, rcx
jge .copydone
mov al, [strbuf+r9]
mov [r11+r9], al
inc r9
jmp .copyloop
.copydone:
add r11, rcx
mov [var_HERE], r11
NEXT
; Runtime companion to ." when compiled: rsi points at the length cell
; that was embedded right after this word's own xt, followed immediately
; by the raw string bytes.
defcode PAREN_DOTQUOTE, '(."', 0
mov rbx, [rsi] ; length (rbx survives the syscall below)
lea rax, [rsi+8] ; string bytes
push rsi
mov rdx, rbx
mov rsi, rax
mov rax, 1
mov rdi, 1
syscall
pop rsi
lea rax, [rbx+8]
add rsi, rax
NEXT
defcode SQUOTE, 'S"', 1
call read_string_lit
mov r10, [var_STATE]
test r10, r10
jnz .compile
lea rax, [strbuf]
push_ds rax
push_ds rcx
NEXT
.compile:
mov r11, [var_HERE]
mov qword [r11], name_PAREN_SQUOTE_CFA
add r11, 8
mov [r11], rcx
add r11, 8
xor r9, r9
.copyloop:
cmp r9, rcx
jge .copydone
mov al, [strbuf+r9]
mov [r11+r9], al
inc r9
jmp .copyloop
.copydone:
add r11, rcx
mov [var_HERE], r11
NEXT
; Runtime companion to S" when compiled: pushes (addr len) of the inline
; string data, same layout as (.").
defcode PAREN_SQUOTE, '(S"', 0
mov rbx, [rsi]
lea rax, [rsi+8]
push_ds rax
push_ds rbx
lea rax, [rbx+8]
add rsi, rax
NEXT
defcode TYPE, "TYPE", 0
pop_ds rcx ; len
pop_ds rax ; addr
push rsi
mov rdx, rcx
mov rsi, rax
mov rax, 1
mov rdi, 1
syscall
pop rsi
NEXT
; A few more common primitives.
defcode NEGATE, "NEGATE", 0
pop_ds rax
neg rax
push_ds rax
NEXT
defcode INCR, "1+", 0
pop_ds rax
inc rax
push_ds rax
NEXT
defcode DECR, "1-", 0
pop_ds rax
dec rax
push_ds rax
NEXT
defcode ABS, "ABS", 0
pop_ds rax
test rax, rax
jns .positive
neg rax
.positive:
push_ds rax
NEXT
defcode MIN, "MIN", 0
pop_ds rax ; b
pop_ds rbx ; a
cmp rax, rbx
jl .done ; b < a -> keep b
mov rax, rbx
.done:
push_ds rax
NEXT
defcode MAX, "MAX", 0
pop_ds rax ; b
pop_ds rbx ; a
cmp rax, rbx
jg .done ; b > a -> keep b
mov rax, rbx
.done:
push_ds rax
NEXT
defcode TWODUP, "2DUP", 0
check_ds_depth 2
mov rax, [rbp] ; b
mov rbx, [rbp+8] ; a
push_ds rbx
push_ds rax
NEXT
defcode TWODROP, "2DROP", 0
pop_ds rax
pop_ds rax
NEXT
; CONSTANT: "value CONSTANT name" reads the name off the input stream (like
; VARIABLE) and defines a word that pushes the value back whenever it runs.
defcode CONSTANT, "CONSTANT", 0
call word_ ; NB: word_ uses rbx as scratch, so read the
test rax, rax ; name before popping the value into rbx
jz .done
pop_ds rbx
mov r8, [var_HERE]
mov r9, [var_LATEST]
mov [r8], r9
mov byte [r8+8], 0
mov [r8+9], cl
lea r10, [r8+10]
xor r11, r11
.copy:
cmp r11, rcx
jge .nameend
mov al, [rdi+r11]
mov [r10+r11], al
inc r11
jmp .copy
.nameend:
lea r10, [r10+rcx]
mov qword [r10], DOCON
lea r14, [r10+8]
mov [r14], rbx
mov [var_LATEST], r8
lea r15, [r14+8]
mov [var_HERE], r15
.done:
NEXT
; INTERPRET: the outer interpreter loop's body. Reads one word, looks it
; up, and either compiles or executes it (numbers get pushed or compiled
; as a LIT). Runs forever as part of QUIT_thread.
defcode INTERPRET, "INTERPRET", 0
call word_
test rax, rax
jz .eof
call find_
test rax, rax
jz .maybe_number
mov r8, rax
movzx r9d, byte [r8+8] ; immediate flag
movzx ecx, byte [r8+9]
lea rax, [r8+10+rcx] ; rax = xt (codeword field address)
mov r10, [var_STATE]
test r10, r10
jz .execute
test r9, r9
jnz .execute
mov r11, [var_HERE]
mov [r11], rax
add r11, 8
mov [var_HERE], r11
NEXT
.execute:
jmp [rax] ; tail into the word; NEXT happens inside it
.maybe_number:
call number_
test rdx, rdx
jnz .error
mov r10, [var_STATE]
test r10, r10
jnz .compile_lit
push_ds rax
NEXT
.compile_lit:
mov r11, [var_HERE]
mov qword [r11], name_LIT_CFA
add r11, 8
mov [r11], rax
add r11, 8
mov [var_HERE], r11
NEXT
.error:
push rsi
mov r13, rdi
mov r14, rcx
mov rax, 1
mov rdi, 2
mov rsi, r13
mov rdx, r14
syscall
mov rax, 1
mov rdi, 2
lea rsi, [errmsg]
mov rdx, errmsg_len
syscall
pop rsi
; An unknown word or bad number mid-definition means the definition is
; broken beyond repair (whatever was compiled so far isn't terminated
; by EXIT). Unlink it -- as if ':' had never been typed -- rather than
; leaving a half-built, uncallable word sitting in the dictionary and
; silently staying in compile mode for everything typed afterward.
mov r10, [var_STATE]
test r10, r10
jz .error_done
mov r11, [var_SAVED_LATEST]
mov [var_LATEST], r11
mov qword [var_STATE], 0
.error_done: