-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathlang_s.f
More file actions
1816 lines (1520 loc) · 56.2 KB
/
lang_s.f
File metadata and controls
1816 lines (1520 loc) · 56.2 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
: drop2 [ 0b1_1_0_100010_0_000000010000_11011_11011 comp_instr ] ;
:: \ 10 parse drop2 ;
:: ( 41 parse drop2 ;
\ Can use comments now!
\
\ This file boostraps the Forth language. The outer interpreter / compiler
\ provides only the most fundamental intrinsics; enough for self-assembly.
\ We define basic words in terms of machine instructions, building up from
\ there. Currently only the Arm64 CPU architecture is supported.
\
\ The compiler and language come in two variants: stack-CC and register-CC.
\ This file uses the traditional stack-based calling convention. See the
\ file `./lang.f` for the register-based calling convention.
\ brk 666
: abort [ 0b110_101_00_001_0000001010011010_000_00 comp_instr ] ;
: unreachable abort ;
: nop ;
\ ## Assembler and bitwise ops
\
\ We interleave definitions of assembler words with definitions
\ of various arithmetic words used by the assembler.
\
\ The assembler is also split; some parts are defined
\ further down, after some stack-manipulation words.
: ASM_INSTR_SIZE 4 ;
: ASM_REG_ERR 0 ;
: ASM_REG_DAT_SP_FLOOR 26 ; \ SYNC[asm_arm64_cc_stack_special_regs].
: ASM_REG_DAT_SP 27 ; \ SYNC[asm_arm64_cc_stack_special_regs].
: ASM_REG_INTERP 28 ; \ SYNC[asm_reg_interp].
: ASM_REG_FP 29 ;
: ASM_REG_LR 30 ;
: ASM_REG_SP 31 ;
: ASM_EQ 0b0000 ;
: ASM_NE 0b0001 ;
: ASM_CS 0b0010 ;
: ASM_CC 0b0011 ;
: ASM_MI 0b0100 ;
: ASM_PL 0b0101 ;
: ASM_VS 0b0110 ;
: ASM_VC 0b0111 ;
: ASM_HI 0b1000 ;
: ASM_LS 0b1001 ;
: ASM_GE 0b1010 ;
: ASM_LT 0b1011 ;
: ASM_GT 0b1100 ;
: ASM_LE 0b1101 ;
: ASM_AL 0b1110 ;
: ASM_NV 0b1111 ;
: ASM_PLACEHOLDER 666 ; \ udf 666; used in retropatching.
: asm_pop_x1_x2
\ ldp x1, x2, [x27, -16]!
0b10_101_0_011_1_1111110_00010_11011_00001
;
: asm_push_x1_x2
\ stp x1, x2, [x27], 16
0b10_101_0_001_0_0000010_00010_11011_00001
;
: asm_pop_x1
\ ldr x1, [x27, -8]!
0b11_111_0_00_01_0_111111000_11_11011_00001
;
: asm_push_x1
\ str x1, [x27], 8
0b11_111_0_00_00_0_000001000_01_11011_00001
;
\ Bitwise arithmetic needed for the early stages of the self-assembler.
: or ( i1 i2 -- i3 ) [
asm_pop_x1_x2 comp_instr \ ldp x1, x2, [x27, -16]!
0b1_01_01010_00_0_00010_000000_00001_00001 comp_instr \ orr x1, x1, x2
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
: and ( i1 i2 -- i3 ) [
asm_pop_x1_x2 comp_instr \ ldp x1, x2, [x27, -16]!
0b1_00_01010_00_0_00010_000000_00001_00001 comp_instr \ and x1, x1, x2
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
: xor ( i1 i2 -- i3 ) [
asm_pop_x1_x2 comp_instr \ ldp x1, x2, [x27, -16]!
0b1_10_01010_00_0_00010_000000_00001_00001 comp_instr \ eor x1, x1, x2
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
: lsl ( i1 bits -- i2 ) [
asm_pop_x1_x2 comp_instr \ ldp x1, x2, [x27, -16]!
0b1_0_0_11010110_00010_0010_00_00001_00001 comp_instr \ lsl x1, x1, x2
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
: lsr ( i1 bits -- i2 ) [
asm_pop_x1_x2 comp_instr \ ldp x1, x2, [x27, -16]!
0b1_0_0_11010110_00010_0010_01_00001_00001 comp_instr \ lsr x1, x1, x2
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
: invert ( i1 -- i2 ) [
asm_pop_x1 comp_instr \ ldr x1, [x27, -8]!
0b1_01_01010_00_1_00001_000000_11111_00001 comp_instr \ mvn x1, x1
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
\ Our parsing rules prevent `1+` or `+1` from being a word name.
: inc ( i1 -- i2 ) [
asm_pop_x1 comp_instr \ ldr x1, [x27, -8]!
0b1_0_0_100010_0_000000000001_00001_00001 comp_instr \ add x1, x1, 1
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
\ Our parsing rules prevent `1-` or `-1` from being a word name.
: dec ( i1 -- i2 ) [
asm_pop_x1 comp_instr \ ldr x1, [x27, -8]!
0b1_1_0_100010_0_000000000001_00001_00001 comp_instr \ sub x1, x1, 1
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
: swap ( i1 i2 -- i2 i1 ) [
asm_pop_x1_x2 comp_instr \ ldp x1, x2, [x27, -16]!
0b10_101_0_001_0_0000010_00001_11011_00010 comp_instr \ stp x2, x1, [x27], 16
] ;
: low_bits ( bit_len -- bits ) 1 swap lsl dec ;
: bit_trunc ( imm bit_len -- imm ) low_bits and ;
\ cmp Xn, <imm>
: asm_cmp_imm ( Xn imm -- instr )
10 lsl \ imm
swap 5 lsl or \ Xn
0b1_1_1_100010_0_000000000000_00000_11111 or
;
\ cmp Xn, 0
: asm_cmp_zero ( Xn -- instr ) 0 asm_cmp_imm ;
\ cset Xd, <cond>
: asm_cset ( Xd cond -- instr )
0b1_0_0_11010100_11111_0000_0_1_11111_00000
swap 1 xor 12 lsl or \ cond
or \ Xd
;
: <>0 ( int -- bool ) [
asm_pop_x1 comp_instr \ ldr x1, [x27, -8]!
1 asm_cmp_zero comp_instr \ cmp x1, 0
1 ASM_NE asm_cset comp_instr \ cset x1, ne
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
: =0 ( int -- bool ) [
asm_pop_x1 comp_instr \ ldr x1, [x27, -8]!
1 asm_cmp_zero comp_instr \ cmp x1, 0
1 ASM_EQ asm_cset comp_instr \ cset x1, eq
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
\ Shared by a lot of load and store instructions. 64-bit only.
: asm_pattern_load_store_pair ( Xt1 Xt2 Xn imm7 -- instr_mask )
3 lsr 7 bit_trunc 15 lsl \ imm7
swap 5 lsl or \ Xn
swap 10 lsl or \ Xt2
or \ Xt1
;
\ ldp Xt1, Xt2, [Xn, <imm>]!
: asm_load_pair_pre ( Xt1 Xt2 Xn imm7 -- instr )
asm_pattern_load_store_pair
0b10_101_0_011_1_0000000_00000_00000_00000 or
;
\ ldp Xt1, Xt2, [Xn], <imm>
: asm_load_pair_post ( Xt1 Xt2 Xn imm7 -- instr )
asm_pattern_load_store_pair
0b10_101_0_001_1_0000000_00000_00000_00000 or
;
\ ldp Xt1, Xt2, [Xn, <imm>]
: asm_load_pair_off ( Xt1 Xt2 Xn imm7 -- instr )
asm_pattern_load_store_pair
0b10_101_0_010_1_0000000_00000_00000_00000 or
;
\ stp Xt1, Xt2, [Xn, <imm>]!
: asm_store_pair_pre ( Xt1 Xt2 Xn imm7 -- instr )
asm_pattern_load_store_pair
0b10_101_0_011_0_0000000_00000_00000_00000 or
;
\ stp Xt1, Xt2, [Xn], <imm>
: asm_store_pair_post ( Xt1 Xt2 Xn imm7 -- instr )
asm_pattern_load_store_pair
0b10_101_0_001_0_0000000_00000_00000_00000 or
;
\ stp Xt1, Xt2, [Xn, <imm>]
: asm_store_pair_off ( Xt1 Xt2 Xn imm7 -- instr )
asm_pattern_load_store_pair
0b10_101_0_010_0_0000000_00000_00000_00000 or
;
\ Shared by a lot of load and store instructions.
\ Patches these bits:
\ 0b00_000_0_00_00_0_XXXXXXXXX_00_XXXXX_XXXXX
: asm_pattern_load_store ( Xt Xn imm9 -- instr_mask )
9 bit_trunc 12 lsl \ imm9
swap 5 lsl or \ Xn
or \ Xt
;
\ ldr Xt, [Xn, <imm>]!
: asm_load_pre ( Xt Xn imm9 -- instr )
asm_pattern_load_store
0b11_111_0_00_01_0_000000000_11_00000_00000 or
;
\ ldr Xt, [Xn], <imm>
: asm_load_post ( Xt Xn imm9 -- instr )
asm_pattern_load_store
0b11_111_0_00_01_0_000000000_01_00000_00000 or
;
\ str Xt, [Xn, <imm>]!
: asm_store_pre ( Xt Xn imm9 -- instr )
asm_pattern_load_store
0b11_111_0_00_00_0_000000000_11_00000_00000 or
;
\ str Xt, [Xn], <imm>
: asm_store_post ( Xt Xn imm9 -- instr )
asm_pattern_load_store
0b11_111_0_00_00_0_000000000_01_00000_00000 or
;
\ ldur Xt, [Xn, <imm>]
: asm_load_off ( Xt Xn imm9 -- instr )
asm_pattern_load_store
0b11_111_0_00_01_0_000000000_00_00000_00000 or
;
\ stur Xt, [Xn, <imm>]
: asm_store_off ( Xt Xn imm9 -- instr )
asm_pattern_load_store
0b11_111_0_00_00_0_000000000_00_00000_00000 or
;
\ For unsigned ints only.
\
\ ldur Wt, [Xn, <imm>]
: asm_load_off_32 ( Wt Xn imm9 -- instr )
asm_pattern_load_store
0b10_111_0_00_01_0_000000000_00_00000_00000 or
;
\ stur Wt, [Xn, <imm>]
: asm_store_off_32 ( Wt Xn imm9 -- instr )
asm_pattern_load_store
0b10_111_0_00_00_0_000000000_00_00000_00000 or
;
\ Shared by register-offset load and store instructions.
\ `scale` must be 0 or 1; if 1, offset is multiplied by 8.
: asm_pattern_load_store_with_register_off ( Xt Xn Xm scale -- instr_mask )
<>0 12 lsl \ lsl 3
swap 16 lsl or \ Xm
swap 5 lsl or \ Xn
or \ Xt
;
\ ldr Xt, [Xn, Xm, lsl <scale>]
: asm_load_with_register_off ( Xt Xn Xm scale -- instr )
asm_pattern_load_store_with_register_off
0b11_111_0_00_01_1_00000_011_0_10_00000_00000 or
;
\ str Xt, [Xn, Xm, lsl <scale>]
: asm_store_with_register_off ( Xt Xn Xm scale -- instr )
asm_pattern_load_store_with_register_off
0b11_111_0_00_00_1_00000_011_0_10_00000_00000 or
;
\ Shared by some integer arithmetic instructions.
: asm_pattern_arith_reg ( Xd Xn Xm -- instr_mask )
16 lsl \ Xm
swap 5 lsl or \ Xn
or \ Xd
;
\ Shared by some integer arithmetic and load/store instructions.
: asm_pattern_arith_imm ( Xd Xn imm12 -- instr_mask )
12 bit_trunc 10 lsl \ imm12
swap 5 lsl or \ Xn
or \ Xd
;
\ Immediate offset must be unsigned.
\ ldr Xt, [Xn, <imm>]
: asm_load_scaled_off ( Xt Xn imm9 -- instr )
3 lsr asm_pattern_arith_imm
0b11_111_0_01_01_000000000000_00000_00000 or
;
\ Immediate offset must be unsigned.
\ str Xt, [Xn, <imm>]
: asm_store_scaled_off ( Xt Xn imm9 -- instr )
3 lsr asm_pattern_arith_imm
0b11_111_0_01_00_000000000000_00000_00000 or
;
\ For unsigned bytes only.
\
\ ldrb Wt, [Xn, imm12]
: asm_load_byte_off ( Wt Xn imm12 -- instr )
asm_pattern_arith_imm
0b00_11_1_0_0_1_01_000000000000_00000_00000 or
;
\ strb Wt, [Xn, imm12]
: asm_store_byte_off ( Wt Xn imm12 -- instr )
asm_pattern_arith_imm
0b00_11_1_0_0_1_00_000000000000_00000_00000 or
;
\ str Xd, [x27], 8
: asm_push1 ( Xd -- instr ) ASM_REG_DAT_SP 8 asm_store_post ;
\ ldr Xd, [x27, -8]!
: asm_pop1 ( Xd -- instr ) ASM_REG_DAT_SP -8 asm_load_pre ;
\ stp Xt1, Xt2, [x27], 16
: asm_push2 ( Xt1 Xt2 -- instr ) ASM_REG_DAT_SP 16 asm_store_pair_post ;
\ ldp Xt1, Xt2, [x27, -16]!
: asm_pop2 ( Xt1 Xt2 -- instr ) ASM_REG_DAT_SP -16 asm_load_pair_pre ;
\ add Xd, Xn, <imm12>
: asm_add_imm ( Xd Xn imm12 -- instr )
asm_pattern_arith_imm
0b1_0_0_100010_0_000000000000_00000_00000 or
;
\ add Xd, Xn, Xm
: asm_add_reg ( Xd Xn Xm -- instr )
asm_pattern_arith_reg
0b1_0_0_01011_00_0_00000_000000_00000_00000 or
;
\ sub Xd, Xn, <imm12>
: asm_sub_imm ( Xd Xn imm12 -- instr )
asm_pattern_arith_imm
0b1_1_0_100010_0_000000000000_00000_00000 or
;
\ sub Xd, Xn, Xm
: asm_sub_reg ( Xd Xn Xm -- instr )
asm_pattern_arith_reg
0b1_1_0_01011_00_0_00000_000000_00000_00000 or
;
\ sub Xd, Xn, Xm, lsl 3
: asm_sub_reg_words ( Xd Xn Xm -- instr )
asm_sub_reg
0b0_0_0_00000_00_0_00000_000011_00000_00000 or
;
\ mul Xd, Xn, Xm
: asm_mul ( Xd Xn Xm -- instr )
asm_pattern_arith_reg
0b1_00_11011_000_00000_0_11111_00000_00000 or
;
\ sdiv Xd, Xn, Xm
: asm_sdiv ( Xd Xn Xm -- instr )
asm_pattern_arith_reg
0b1_0_0_11010110_00000_00001_1_00000_00000 or
;
\ msub Xd, Xn, Xm, Xa
: asm_msub ( Xd Xn Xm Xa -- instr )
0b1_00_11011_000_00000_1_00000_00000_00000
swap 10 lsl or \ Xa
swap 16 lsl or \ Xm
swap 5 lsl or \ Xn
or \ Xd
;
\ asr Xd, Xn, imm6
: asm_asr_imm ( Xd Xn imm6 -- instr )
asm_pattern_arith_reg
0b1_00_100110_1_000000_111111_00000_00000 or
;
\ asr Xd, Xn, Xm
: asm_asr_reg ( Xd Xn Xm -- instr )
asm_pattern_arith_reg
0b1_0_0_11010110_00000_0010_10_00000_00000 or
;
\ Arithmetic (sign-preserving) right shift.
: asr ( i1 bits -- i2 ) [
asm_pop_x1_x2 comp_instr \ ldp x1, x2, [x27, -16]!
1 1 2 asm_asr_reg comp_instr \ asr x1, x1, x2
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
\ eor Xd, Xn, Xm
: asm_eor_reg ( Xd Xn Xm -- instr )
asm_pattern_arith_reg
0b1_10_01010_00_0_00000_000000_00000_00000 or
;
: asm_pattern_arith_csel ( Xd Xn Xm cond -- instr_mask )
[ 3 asm_pop1 comp_instr ] \ Stash `cond`.
asm_pattern_arith_reg \ Xd Xm Xn
[ 3 asm_push1 comp_instr ] \ Pop `cond`.
12 lsl or \ cond
;
\ csel Xd, Xn, Xm, <cond>
: asm_csel ( Xd Xn Xm cond -- instr )
asm_pattern_arith_csel
0b1_0_0_11010100_00000_0000_0_0_00000_00000 or
;
\ csneg Xd, Xn, Xm, <cond>
: asm_csneg ( Xd Xn Xm cond -- instr )
asm_pattern_arith_csel
0b1_1_0_11010100_00000_0000_0_1_00000_00000 or
;
\ cmp Xn, Xm
: asm_cmp_reg ( Xn Xm -- instr )
16 lsl \ Xm
swap 5 lsl or \ Xn
0b1_1_1_01011_00_0_00000_000_000_00000_11111 or
;
\ b <off>
: asm_branch ( off26 -- instr )
2 lsr \ Offset is implicitly times 4.
26 bit_trunc \ Offset may be negative.
0b0_00_101_00000000000000000000000000 or
;
\ b.<cond> <off>
: asm_branch_cond ( off19 cond -- instr )
swap 2 asr 19 bit_trunc 5 lsl \ `off19`; implicitly times 4.
or \ cond
0b010_101_00_0000000000000000000_0_0000 or
;
\ `off19` offset is implicitly times 4 and can be negative.
: asm_pattern_cmp_branch ( Xt off19 -- instr_mask )
2 lsr 19 bit_trunc 5 lsl \ off19
or \ Xt
;
\ cbz x1, <off>
: asm_cmp_branch_zero ( Xt off19 -- instr )
asm_pattern_cmp_branch
0b1_011010_0_0000000000000000000_00000 or
;
\ cbnz x1, <off>
: asm_cmp_branch_not_zero ( Xt off19 -- instr )
asm_pattern_cmp_branch
0b1_011010_1_0000000000000000000_00000 or
;
\ Same as `0 pick`.
: dup ( i1 -- i1 i1 ) [
1 ASM_REG_DAT_SP -8 asm_load_off comp_instr \ ldur x1, [x27, -8]
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
\ neg Xd, Xd
: asm_neg ( Xd -- instr )
dup 16 lsl or \ Xt
0b1_1_0_01011_00_0_00000_000000_11111_00000 or
;
\ eor <reg>, <reg>, <reg>
: asm_zero_reg ( reg -- instr ) dup dup asm_eor_reg ;
: asm_mov_reg ( Xd Xm -- instr )
0b1_01_01010_00_0_00000_000000_11111_00000
swap 16 lsl or \ Xm
or \ Xd
;
\ Immediate must be unsigned.
: asm_mov_imm ( Xd imm -- instr )
0b1_10_100101_00_0000000000000000_00000
swap 5 lsl or \ imm
or \ Xd
;
\ mvn Xd, Xm
: asm_mvn ( Xd Xm -- instr )
16 lsl \ Xm
or \ Xd
0b1_01_01010_00_1_00000_000000_11111_00000 or
;
\ `ret x30`; requires caution with SP / FP.
: asm_ret 0b110_101_1_0_0_10_11111_0000_0_0_11110_00000 ;
: asm_nop 0b110_101_01000000110010_0000_000_11111 ;
\ svc 666
: asm_svc 0b110_101_00_000_0000001010011010_000_01 ;
\ ## Compilation-related words
\
\ These words are used for metaprogramming
\ and mostly rely on compiler intrinsics.
\
\ Unlike in standard Forth, we use separate wordlists for
\ regular and compile-time / immediate words. As a result,
\ we have to define parsing words like "compile" in pairs:
\ one tick seeks in the regular wordlist, and double tick
\ seeks in the compile-time wordlist.
\ SYNC[wordlist_enum].
: WORDLIST_EXEC 1 ;
: WORDLIST_COMP 2 ;
\ Non-immediate replacement for standard `literal`.
: comp_push ( C: num -- ) ( E: -- num )
1 comp_load \ ldr x1, <num>
asm_push_x1 comp_instr \ str x1, [x27], 8
;
: next_word ( wlist "word" -- exec_tok ) parse_word find_word ;
: tick_next ( C: wlist "word" -- ) ( E: -- exec_tok ) next_word comp_push ;
:: ' WORDLIST_EXEC tick_next ;
:: '' WORDLIST_COMP tick_next ;
: inline_next ( wlist "word" -- ) ( E: <word> ) next_word inline_word ;
:: inline' WORDLIST_EXEC inline_next ;
:: inline'' WORDLIST_COMP inline_next ;
\ "execute" is renamed from standard "postpone".
: execute_next ( wlist "word" -- ) ( E: <word> ) next_word comp_call ;
:: execute' WORDLIST_EXEC execute_next ;
:: execute'' WORDLIST_COMP execute_next ;
: compile_next ( wlist "word" -- ) next_word comp_push ' comp_call comp_call ;
:: compile' WORDLIST_EXEC compile_next ;
:: compile'' WORDLIST_COMP compile_next ;
\ ## Stack manipulation
: drop ( val -- ) [
ASM_REG_DAT_SP ASM_REG_DAT_SP 8 asm_sub_imm comp_instr \ sub x27, x27, 8
] ;
\ Same as `1 pick 1 pick`.
: dup2 ( i1 i2 -- i1 i2 i1 i2 ) [
1 2 ASM_REG_DAT_SP -16 asm_load_pair_off comp_instr \ ldp x1, x2, [x27, -16]
1 2 asm_push2 comp_instr \ stp x1, x2, [x27], 16
] ;
\ Same as `swap drop`.
: nip ( i1 i2 -- i2 ) [
asm_pop_x1 comp_instr \ ldr x1, [x27, -8]!
1 ASM_REG_DAT_SP -8 asm_store_off comp_instr \ stur x1, [x27, -8]
] ;
\ Same as `swap2 drop2`.
: nip2 ( i1 i2 i3 i4 -- i3 i4 ) [
asm_pop_x1_x2 comp_instr \ ldp x1, x2, [x27, -16]!
1 2 ASM_REG_DAT_SP -16 asm_store_pair_off comp_instr \ stp x1, x2, [x27, -16]
] ;
\ Same as `1 pick`.
: over ( i1 i2 -- i1 i2 i1 ) [
1 ASM_REG_DAT_SP -16 asm_load_off comp_instr \ ldur x1, [x27, -16]
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
\ Same as `3 pick 3 pick`.
: over2 ( i1 i2 i3 i4 -- i1 i2 i3 i4 i1 i2 ) [
1 2 ASM_REG_DAT_SP -32 asm_load_pair_off comp_instr \ ldp x1, x2, [x27, -32]
asm_push_x1_x2 comp_instr \ stp x1, x2, [x27], 16
] ;
\ Same as `3 roll 3 roll`.
: swap2 ( i1 i2 i3 i4 -- i3 i4 i1 i2 ) [
1 2 ASM_REG_DAT_SP -32 asm_load_pair_off comp_instr \ ldp x1, x2, [x27, -32]
3 4 ASM_REG_DAT_SP -16 asm_load_pair_off comp_instr \ ldp x3, x4, [x27, -16]
3 4 ASM_REG_DAT_SP -32 asm_store_pair_off comp_instr \ stp x3, x4, [x27, -32]
1 2 ASM_REG_DAT_SP -16 asm_store_pair_off comp_instr \ stp x1, x2, [x27, -16]
] ;
\ Same as `-rot swap rot`.
: swap_over ( i1 i2 i3 -- i2 i1 i3 ) [
1 2 ASM_REG_DAT_SP -24 asm_load_pair_off comp_instr \ ldp x1, x2, [x27, -24]
2 1 ASM_REG_DAT_SP -24 asm_store_pair_off comp_instr \ stp x2, x1, [x27, -24]
] ;
\ Same as `over -rot`.
: dup_over ( i1 i2 -- i1 i1 i2 ) [
1 2 ASM_REG_DAT_SP -16 asm_load_pair_off comp_instr \ ldp x1, x2, [x27, -16]
1 2 ASM_REG_DAT_SP -8 asm_store_pair_off comp_instr \ stp x1, x2, [x27, -8]
ASM_REG_DAT_SP ASM_REG_DAT_SP 8 asm_add_imm comp_instr \ add x27, x27, 8
] ;
\ Same as `dup rot`.
: tuck ( i1 i2 -- i2 i1 i2 ) [
1 2 ASM_REG_DAT_SP -16 asm_load_pair_off comp_instr \ ldp x1, x2, [x27, -16]
2 1 ASM_REG_DAT_SP -16 asm_store_pair_off comp_instr \ stp x2, x1, [x27, -16]
2 asm_push1 comp_instr \ str x2, [x27], 8
] ;
: tuck2 ( i1 i2 i3 i4 -- i3 i4 i1 i2 i3 i4 ) [
inline' swap2
3 4 asm_push2 comp_instr \ stp x3, x4, [x27], 16
] ;
\ Same as `2 roll`.
: rot ( i1 i2 i3 -- i2 i3 i1 ) [
1 ASM_REG_DAT_SP -24 asm_load_off comp_instr \ ldur x1, [x27, -24]
2 3 ASM_REG_DAT_SP -16 asm_load_pair_off comp_instr \ ldp x2, x3, [x27, -16]
2 3 ASM_REG_DAT_SP -24 asm_store_pair_off comp_instr \ stp x2, x3, [x27, -24]
1 ASM_REG_DAT_SP -8 asm_store_off comp_instr \ stur x1, [x27, -8]
] ;
: -rot ( i1 i2 i3 -- i3 i1 i2 ) [
1 ASM_REG_DAT_SP -24 asm_load_off comp_instr \ ldur x1, [x27, -24]
2 3 ASM_REG_DAT_SP -16 asm_load_pair_off comp_instr \ ldp x2, x3, [x27, -16]
3 ASM_REG_DAT_SP -24 asm_store_off comp_instr \ stur x3, [x27, -24]
1 2 ASM_REG_DAT_SP -16 asm_store_pair_off comp_instr \ stp x1, x2, [x27, -16]
] ;
: rot2 ( i1 i2 i3 i4 i5 i6 -- i3 i4 i5 i6 i1 i2 ) [
1 2 ASM_REG_DAT_SP -48 asm_load_pair_off comp_instr \ ldp x1, x2, [x27, -48]
3 4 ASM_REG_DAT_SP -32 asm_load_pair_off comp_instr \ ldp x3, x4, [x27, -32]
5 6 ASM_REG_DAT_SP -16 asm_load_pair_off comp_instr \ ldp x5, x6, [x27, -16]
3 4 ASM_REG_DAT_SP -48 asm_store_pair_off comp_instr \ stp x3, x4, [x27, -48]
5 6 ASM_REG_DAT_SP -32 asm_store_pair_off comp_instr \ stp x5, x6, [x27, -32]
1 2 ASM_REG_DAT_SP -16 asm_store_pair_off comp_instr \ stp x1, x2, [x27, -16]
] ;
\ Pushes the stack item found at the given index, duplicating it.
: pick ( … ind -- … [ind] ) [
asm_pop_x1 comp_instr \ ldr x1, [x27, -8]!
1 1 asm_mvn comp_instr \ mvn x1, x1
1 ASM_REG_DAT_SP 1 1 asm_load_with_register_off comp_instr \ ldr x1, [x27, x1, lsl 3]
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
\ FIFO version of `pick`: starts from stack bottom.
: pick0 ( … ind -- … [ind] ) [
asm_pop_x1 comp_instr \ ldr x1, [x27, -8]!
1 26 1 1 asm_load_with_register_off comp_instr \ ldr x1, [x26, x1, lsl 3]
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
\ Overwrite the cell at the given index with the given value.
: bury ( … val ind -- … ) [
asm_pop_x1_x2 comp_instr \ ldp x1, x2, [x27, -16]!
2 2 asm_mvn comp_instr \ mvn x2, x2
1 ASM_REG_DAT_SP 2 1 asm_store_with_register_off comp_instr \ str x1, [x27, x2, lsl 3]
] ;
: flip ( i1 i2 i3 -- i3 i2 i1 ) [
1 ASM_REG_DAT_SP -24 asm_load_off comp_instr \ ldr x1, [x27, -24]
2 ASM_REG_DAT_SP -8 asm_load_off comp_instr \ ldr x2, [x27, -8]
2 ASM_REG_DAT_SP -24 asm_store_off comp_instr \ str x2, [x27, -24]
1 ASM_REG_DAT_SP -8 asm_store_off comp_instr \ str x1, [x27, -8]
] ;
\ ## Arithmetic
: negate ( i1 -- i2 ) [
asm_pop_x1 comp_instr \ ldr x1, [x27, -8]!
1 asm_neg comp_instr \ neg x1, x1
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
: + ( i1 i2 -- i3 ) [
asm_pop_x1_x2 comp_instr \ ldp x1, x2, [x27, -16]!
1 1 2 asm_add_reg comp_instr \ add x1, x1, x2
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
: - ( i1 i2 -- i3 ) [
asm_pop_x1_x2 comp_instr \ ldp x1, x2, [x27, -16]!
1 1 2 asm_sub_reg comp_instr \ sub x1, x1, x2
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
: * ( i1 i2 -- i3 ) [
asm_pop_x1_x2 comp_instr \ ldp x1, x2, [x27, -16]!
1 1 2 asm_mul comp_instr \ mul x1, x1, x2
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
: / ( i1 i2 -- i3 ) [
asm_pop_x1_x2 comp_instr \ ldp x1, x2, [x27, -16]!
1 1 2 asm_sdiv comp_instr \ sdiv x1, x1, x2
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
: mod ( i1 i2 -- i3 ) [
asm_pop_x1_x2 comp_instr \ ldp x1, x2, [x27, -16]!
3 1 2 asm_sdiv comp_instr \ sdiv x3, x1, x2
1 3 2 1 asm_msub comp_instr \ msub x1, x3, x2, x1
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
: /mod ( dividend divisor -- rem quo ) [
1 2 asm_pop2 comp_instr \ ldp x1, x2, [x27, -16]!
3 1 2 asm_sdiv comp_instr \ sdiv x3, x1, x2
1 3 2 1 asm_msub comp_instr \ msub x1, x3, x2, x1
1 3 asm_push2 comp_instr \ stp x1, x3, [x27], 16
] ;
: abs ( ±int -- +int ) [
asm_pop_x1 comp_instr \ ldr x1, [x27, -8]!
1 asm_cmp_zero comp_instr \ cmp x1, 0
1 1 1 ASM_PL asm_csneg comp_instr \ cneg x1, x1, pl
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
: min ( i1 i2 -- i1|i2 ) [
asm_pop_x1_x2 comp_instr \ ldp x1, x2, [x27, -16]!
1 2 asm_cmp_reg comp_instr \ cmp x1, x2
1 1 2 ASM_LT asm_csel comp_instr \ csel x1, x1, x2, lt
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
: max ( i1 i2 -- i1|i2 ) [
asm_pop_x1_x2 comp_instr \ ldp x1, x2, [x27, -16]!
1 2 asm_cmp_reg comp_instr \ cmp x1, x2
1 1 2 ASM_GT asm_csel comp_instr \ csel x1, x1, x2, gt
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
: cell 8 ;
: cells ( len -- size ) 3 lsl ;
: /cells ( size -- len ) 3 asr ;
: +cell ( adr -- adr ) cell + ;
: -cell ( adr -- adr ) cell - ;
\ ## Assembler continued
\ lsl Xd, Xn, <imm>
: asm_lsl_imm ( Xd Xn imm6 -- instr )
dup negate 64 mod 6 bit_trunc 16 lsl \ immr
63 rot - 6 bit_trunc 10 lsl or \ imms
swap 5 lsl or \ Xn
or \ Xd
0b1_10_100110_1_000000_000000_00000_00000 or
;
\ tbz Xt, <bit>, <imm>
: asm_test_bit_branch_zero ( Xt bit imm14 -- instr )
2 lsr 14 bit_trunc 5 lsl \ imm14
over 5 lsr 31 lsl or \ b5
swap 5 bit_trunc 19 lsl or \ b40
or \ Xt
0b0_01_101_1_0_00000_00000000000000_00000 or
;
\ br Xn
: asm_branch_to_reg ( Xn -- instr )
5 lsl
0b110_101_1_0_0_00_11111_0000_0_0_00000_00000 or
;
\ blr Xn
: asm_branch_link_reg ( Xn -- instr )
5 lsl
0b110_101_1_0_0_01_11111_0000_0_0_00000_00000 or
;
\ orr Xd, Xn, #(1 << bit)
\ TODO add general-purpose `orr`.
: asm_orr_bit ( Xd Xn bit -- instr )
64 swap - 16 lsl \ immr
swap 5 lsl or \ Xn
or \ Xd
0b1_01_100100_1_000000_000000_00000_00000 or
;
: asm_comp_cset_reg ( C: cond -- ) ( E: i1 i2 -- bool )
1 2 asm_pop2 comp_instr \ ldp x1, x2, [x27, -16]!
1 2 asm_cmp_reg comp_instr \ cmp x1, x2
1 swap asm_cset comp_instr \ cset x1, <cond>
1 asm_push1 comp_instr \ str x1, [x27], 8
;
: asm_comp_cset_zero ( C: cond -- ) ( E: i1 i2 -- bool )
1 asm_pop1 comp_instr \ ldr x1, [x27, -8]!
1 asm_cmp_zero comp_instr \ cmp x1, 0
1 swap asm_cset comp_instr \ cset x1, <cond>
1 asm_push1 comp_instr \ str x1, [x27], 8
;
\ ## Numeric comparison
\ https://gforth.org/manual/Numeric-comparison.html
: = ( i1 i2 -- bool ) [ ASM_EQ asm_comp_cset_reg ] ;
: <> ( i1 i2 -- bool ) [ ASM_NE asm_comp_cset_reg ] ;
: > ( i1 i2 -- bool ) [ ASM_GT asm_comp_cset_reg ] ;
: < ( i1 i2 -- bool ) [ ASM_LT asm_comp_cset_reg ] ;
: <= ( i1 i2 -- bool ) [ ASM_LE asm_comp_cset_reg ] ;
: >= ( i1 i2 -- bool ) [ ASM_GE asm_comp_cset_reg ] ;
: <0 ( num -- bool ) [ ASM_LT asm_comp_cset_zero ] ; \ Or `MI`.
: >0 ( num -- bool ) [ ASM_GT asm_comp_cset_zero ] ;
: <=0 ( num -- bool ) [ ASM_LE asm_comp_cset_zero ] ;
: >=0 ( num -- bool ) [ ASM_GE asm_comp_cset_zero ] ; \ Or `PL`.
: odd ( i1 -- bool ) 1 and ;
\ ## Memory load / store
: @ ( adr -- val ) [
asm_pop_x1 comp_instr \ ldr x1, [x27, -8]!
1 1 0 asm_load_off comp_instr \ ldur x1, [x1]
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
: ! ( val adr -- ) [
asm_pop_x1_x2 comp_instr \ ldp x1, x2, [x27, -16]!
1 2 0 asm_store_off comp_instr \ stur x1, [x2]
] ;
: @2 ( adr -- val0 val1 ) [
asm_pop_x1 comp_instr \ ldr x1, [x27, -8]!
1 2 1 0 asm_load_pair_off comp_instr \ ldp x1, x2, [x1]
asm_push_x1_x2 comp_instr \ stp x1, x2, [x27], 16
] ;
: !2 ( val0 val1 adr -- val0 val1 ) [
3 asm_pop1 comp_instr \ ldr x3, [x27, -8]!
asm_pop_x1_x2 comp_instr \ ldp x1, x2, [x27, -16]!
1 2 3 0 asm_store_pair_off comp_instr \ stp x1, x2, [x3]
] ;
\ 32-bit version of `@`. Used for C `uint`.
: @32 ( adr -- val ) [
asm_pop_x1 comp_instr \ ldr x1, [x27, -8]!
1 1 0 asm_load_off_32 comp_instr \ ldur w1, [x1]
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
\ 32-bit version of `!`. Used for instructions and C `uint`.
: !32 ( val adr -- ) [
asm_pop_x1_x2 comp_instr \ ldp x1, x2, [x27, -16]!
1 2 0 asm_store_off_32 comp_instr \ str w1, x2
] ;
: off! ( adr -- ) 0 swap ! ;
: on! ( adr -- ) 1 swap ! ;
\ ## Stack introspection and manipulation
\ Floor of data stack.
\ str x26, [x27], 8
: sp0 ( -- adr ) [ ASM_REG_DAT_SP_FLOOR asm_push1 comp_instr ] ;
\ Pushes the address of the next writable stack cell.
\ Like `sp@` in Gforth but renamed to make sense.
\ Note: our integer stack is empty-ascending.
\
\ Should have been `str x27, [x27], 8`, but Arm64 has a quirk where an
\ address-modifying load or store which also uses the address register
\ in the value position is considered to be "unpredictable". Different
\ CPUs are allowed to handle this differently; on Apple Silicon chips,
\ this is considered a "bad instruction" and blows up.
: sp ( -- adr ) [
ASM_REG_DAT_SP ASM_REG_DAT_SP 0 asm_store_off comp_instr \ stur x27, [x27]
ASM_REG_DAT_SP ASM_REG_DAT_SP 8 asm_add_imm comp_instr \ add x27, x27, 8
] ;
\ Sets the stack pointer register to the given address.
\ Uses two instructions for the same reason as the above.
: sp! ( adr -- ) [
asm_pop_x1 comp_instr \ ldr x1, [x27, -8]!
ASM_REG_DAT_SP 1 asm_mov_reg comp_instr \ mov x27, x1
] ;
\ Stack introspection doesn't need to be optimal.
: sp_at ( ind -- ptr ) cells sp0 + ;
: stack_len ( -- len ) sp sp0 - /cells ;
: stack_clear ( … -- ) sp0 sp! ;
: stack_set_len ( … len -- … ) sp_at sp! ;
: stack+ ( … diff -- … ) dec cells sp swap + sp! ;
: stack- ( … diff -- … ) inc cells sp swap - sp! ;
\ ## Characters and strings
: c@ ( str -- char ) [
asm_pop_x1 comp_instr \ ldr x1, [x27, -8]!
1 1 0 asm_load_byte_off comp_instr \ ldrb x1, [x1]
asm_push_x1 comp_instr \ str x1, [x27], 8
] ;
: c! ( char adr -- ) [
asm_pop_x1_x2 comp_instr \ ldp x1, x2, [x27, -16]!
1 2 0 asm_store_byte_off comp_instr \ strb x1, [x2]
] ;
: parse_char ( "str" -- char ) parse_word drop c@ ;
: char' ( E: "str" -- char ) parse_char ;
:: char' ( C: "str" -- ) ( E: -- char ) parse_char comp_push ;
\ Interpretation semantics of standard `s"`.
\ The parser ensures null-termination.
: parse_str ( E: "str" -- cstr len ) char' " parse ;
\ Compilation semantics of standard `s"`.
: comp_str ( C: "str" -- ) ( E: -- cstr len )
parse_str tuck ( len str len )
inc \ Reserve 1 more for null byte.
alloc_data 1 comp_page_addr \ `adrp x1, <page>` & `add x1, x1, <pageoff>`
2 comp_load \ ldr x2, <len>
asm_push_x1_x2 comp_instr \ stp x1, x2, [x27], 16
;
: comp_cstr ( C: "str" -- ) ( E: -- cstr )
parse_str inc \ Reserve 1 more for null byte.
alloc_data 1 comp_page_addr \ `adrp x1, <page>` & `add x1, x1, <pageoff>`
asm_push_x1 comp_instr \ str x1, [x27], 8
;
\ Parses the input until the terminating quote, and returns the address and
\ length of the resulting string. In interpretation mode, the string buffer
\ is reused for each call to `parse`, so the resulting string is only valid
\ until the next `parse` invocation. In compilation mode, the string buffer
\ is statically allocated and unique. The string is always null-terminated,
\ and can be safely passed to many `libc` procedures by pointer alone.
: s" ( E: "str" -- cstr len ) parse_str ;
:: s" ( C: "str" -- ) ( E: -- cstr len ) comp_str ;
\ C-style string literal. Like `s"` but returns only the address
\ without the length. The string is always null-terminated.
: " ( E: "str" -- cstr ) parse_str drop ;
:: " ( C: "str" -- ) ( E: -- cstr ) comp_cstr ;
\ ## Variables
\
\ Unlike in ANS Forth, variables take an initial value.
\
\ We could easily define variables without compiler support,
\ allocating memory via libc. The reason for special support
\ such as `alloc_data` is compatibility with AOT compilation
\ which is planned for later.
\ For words which define words. Kinda like `create`.
:: #word_beg compile' : ;
:: #word_end compile'' ; 0 comp_only ;
\ Similar to standard `constant`.
: let: ( C: val -- ) ( E: -- val ) #word_beg comp_push #word_end ;
0 let: nil
0 let: false
1 let: true
\ Similar to standard `variable`.
: var: ( C: init "name" -- ) ( E: -- adr )
#word_beg
sp cell - \ Address of the `init` value.
cell alloc_data ( -- adr )
1 comp_page_addr \ `adrp x1, <page>` & `add x1, x1, <pageoff>`
asm_push_x1 comp_instr \ str x1, [x27], 8
drop \ Don't need the `init` value anymore.
#word_end
;
\ Similar to the standard idiom `create <name> N allot`.
\ Creates a global variable which refers to a buffer of
\ at least the given capacity in bytes.
: buf: ( C: cap "name" -- ) ( E: -- adr cap )
#word_beg
dup nil swap alloc_data ( -- adr )
1 comp_page_addr \ `adrp x1, <page>` & `add x1, x1, <pageoff>`
asm_push_x1 comp_instr \ str x1, [x27], 8
comp_push \ str <cap>, [x27], 8
#word_end
;
\ Shortcut for the standard idiom `create <name> N allot`.
\ Like `buf:` but doesn't return capacity.
: mem: ( C: cap "name" -- ) ( E: -- adr )
#word_beg