-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathlisp.nasm
1718 lines (1589 loc) · 49.5 KB
/
lisp.nasm
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
bits 64
; Code ------------------------------------------------------------------------
; We gebruiken het aanroepgebruik waarbij de aanroeper alles zelf opslaat,
; behalve de volgende registers:
; rbx (wijst naar de huidige omgeving)
; rsp (wijst naar de stapel)
; De aangeroepen code zorgt ervoor dat rbx teruggezet wordt.
; Bij aanroepen wijst rsp naar het terugkeeradres,
; en bij terugkeren wijst het een stapelplek lager.
; (Maar zie ook het speciale geval!)
;
; Daarnaast wordt rax gebruikt voor voorwerpen en resultaten:
; bij het aanroepen van code staat het voorwerp in rax,
; en het resultaat van de aanroep staat daar ook.
; Ten slotte staat bij aanroepen het object in rsi.
; (Dat wil zeggen, bij uitvoeren, doorverwijzen, afmeten en opruimen.)
; Allebei deze registers moeten ook opgeruimd worden door de aanroeper.
;
; Een speciaal geval is het teruggeven van een lijst wijzers:
; in dat geval is rax het aantal wijzers dat teruggegeven wordt,
; en staan de wijzers zelf bovenop de stapel.
; (Bovenop dus de wijzers van de lijst, daaronder de overslawijzer enzovoort.)
;
; Zoals vermeld is de top van de stapel het terugkeeradres.
; Daaronder staat de overslawijzer naar het volgende terugkeeradres.
; Daaronder staan willekeurig veel wijzers naar objecten.
; Daaronder staat weer een terugkeeradres (waar de overslawijzer heenwijst).
; (Maar zie ook het speciale geval hierboven!)
; Dit is nodig om de vuilnisopruiming goed te laten verlopen.
; Objecten --------------------------------------------------------------------
; We gebruiken een dynamisch dataformaat om onze gegevens op te slaan.
; Alle informatie om uit te voeren en op te ruimen staat in code tussen de data.
; Dat betekent dat we goed overweg kunnen met exotische objecten.
; In principe hebben we veel objecten met hetzelfde formaat,
; dus we gebruiken een tweetrapsformaat.
; Een object begint met een wijzer, de `beschrijving'.
; Die wijst naar een structuur met de ``echte'' informatie.
;
; Elk object ziet er dus uit als:
; +- - - - - - - - +- - - ... +
; | beschrijving | data |
; +- - - - - - - - +- - - ... +
;
; Een beschrijving bestaat uit de volgende onderdelen, in deze volgorde:
; * `overbeschrijving', 64 bits; (voorlopig) gelijk aan 0.
; * `uitvoering', 64 bits; wijst naar code.
; * `afmeting', 64 bits; wijst naar code.
; * `doorverwijzing', 64 bits; wijst naar code.
; * `opruiming', 64 bits; wijst naar code.
;
; De overbeschrijving is gereserveerd voor toekomstig gebruik.
;
; De uitvoering wijst naar code die een voorwerp neemt (in rax)
; en een object (in rsi), en het object toepast op dat voorwerp.
;
; De afmeting wijst naar code die het adres van een object neemt (in rsi)
; en de lengte in bytes geeft, inclusief beschrijving.
;
; De doorverwijzing wijst naar code die het adres van een object neemt (in rsi)
; en een lijst van adressen van wijzers geeft.
; (Let op: als je bij het uitvoeren een nieuwe beschrijving aanmaakt,
; telt dit ook als een wijzer!)
;
; De opruiming wijst naar code die het adres van een object neemt (in rsi)
; en eventuele troep (zoals geopende bestanden) opruimt.
; (Deze ruimt beter *niet* objecten uit de doorverwijzing op!)
; Definities ------------------------------------------------------------------
; Een definitie is niets anders dan een adres een naam geven.
; De volgende macro's zorgen ervoor dat het net iets makkelijker gaat.
; Definieer een symbool om verderop te gebruiken.
; Liever wil je een afgeleid macro gebruiken,
; zodat je zeker weet dat je het goede soort symbool definieert.
%macro DEF 1
global %1
%1:
%endmacro
; Definieer een symbool dat wijst naar code.
%macro DEF_CODE 1
section .text
DEF %1
%endmacro
; Definieer een symbool dat wijst naar data.
%macro DEF_DATA 1
section .data
DEF %1
%endmacro
; Definieer een symbool dat wijst naar onwijzigbare data.
%macro DEF_VAST 1
section .rodata
DEF %1
%endmacro
; Definieer een type met een unieke bewoner.
; Dit kun je bijvoorbeeld gebruiken voor losse symbolen als `niks',
; en/of voor speciale resultaatwaarden.
%macro DEF_UNIEK 1
DEF_VAST %1
dq 0
dq ongeldig
dq ongeldig
dq ongeldig
%endmacro
; Koppels ---------------------------------------------------------------------
; Zoals het een lisp betaamt, draait alles om koppels.
; Een koppel is niets anders dan een beschrijving gevolgd door twee wijzers.
; De eerste heet `de kop' en de andere `het pel'. (Hier is over nagedacht!)
; Deze wijzers wijzen uiteraard ook weer naar andere koppels,
; maar je wilt hier niet oneindig diep mee gaan.
; Daarom is er een speciaal symbool (genaamd `niks'),
; dat op geheugenplek 0 staat,
; en staat voor "hier is niets te vinden".
; Dit wordt bijvoorbeeld gebruikt om het einde van lijsten aan te geven.
DEF_UNIEK niks
; Benodigdheden ---------------------------------------------------------------
; We definiëren wat afkortingen en speciale constanten.
; Zie <asm/unistd_64.h> voor deze definities.
syscall_read equ 0
syscall_write equ 1
syscall_brk equ 12
syscall_exit equ 60
; Code, deel 2 ----------------------------------------------------------------
; Voordat we verder gaan, moeten we eerst wat macro's maken.
; In deze macro's staat basale code voor dingen als een object toepassen.
; Geef de beschrijving van een object.
; De beschrijving komt in %1.
; Het object staat in %2.
; Past alleen %1 aan.
%macro NEEM_BESCHRIJVING 2
mov %1, [%2]
%endmacro
; Geef de uitvoering van een object.
; De uitvoering komt in %1.
; Het object staat in %2.
; Past alleen %1 aan.
%macro NEEM_UITVOERING 2
NEEM_BESCHRIJVING %1, %2
mov %1, [%1 + 8]
%endmacro
; Geef de afmeting van een object.
; De afmeting komt in %1.
; Het object staat in %2.
; Past alleen %1 aan.
%macro NEEM_AFMETING 2
NEEM_BESCHRIJVING %1, %2
mov %1, [%1 + 16]
%endmacro
; Geef de doorverwijzing van een object.
; De afmeting komt in %1.
; Het object staat in %2.
; Past alleen %1 aan.
%macro NEEM_DOORVERWIJZING 2
NEEM_BESCHRIJVING %1, %2
mov %1, [%1 + 24]
%endmacro
; Pas het object in rsi toe op rax.
; Past de stapel niet aan, daar moet je zelf voor zorgen.
; Sloopt rcx.
%macro PAS_TOE 0
NEEM_UITVOERING rcx, rsi
jmp rcx
%endmacro
; Stop een aanroepstuk op de stapel.
; Deze macro's horen bij elkaar, en kun je gebruiken als:
; BEGIN_BEWAREN
; push een_voorwerp
; push ander_voorwerp
; EIND_BEWAREN
; push terugkeeradres
;
; BEGIN_BEWAREN en EIND_BEWAREN gebruiken rbp om dingen door te geven,
; die wordt gesloopt en mag tussendoor niet veranderen.
;
; Heb je niets om te bewaren in je aanroepstuk, gebruik dan BEWAAR_NIETS.
%macro BEGIN_BEWAREN 0
mov rbp, rsp
%endmacro
%macro EIND_BEWAREN 0
push rbp
%endmacro
%macro BEWAAR_NIETS 0
push rsp
%endmacro
; Het tegenovergestelde van BEWAREN:
; haal een aanroepstuk van de stapel.
%macro VERGEET 0
mov rsp, [rsp]
%endmacro
; Stop uitvoering met resultaatcode %1.
%macro STOP 1
mov rdi, %1
mov rax, syscall_exit
syscall
%endmacro
; Geef een foutmelding, met fouttekst in %1.
%macro FOUTMELDING 1
mov rsi, %1
call foutmelding
%endmacro
; Geef een foutmelding, met fouttekst in rsi.
DEF_CODE foutmelding
mov rax, rsi
call doe_schrijven
STOP 1
; Hulpcode om te gebruiken als een operatie ongeldig is.
; De fouttekst wordt gedefinieerd nadat MAAK_STRENG bestaat.
DEF_CODE ongeldig
FOUTMELDING ongeldig_fouttekst
; Hulpcode om te gebruiken als er niets hoeft te gebeuren.
DEF_CODE meteen_klaar
ret
; Hulpcode om te gebruiken als een object alleen beschrijving heeft.
DEF_CODE geen_afmeting
mov rax, 8
ret
; Hulpcode om te gebruiken als er geen doorverwijzing gebeurt.
DEF_CODE geen_doorverwijzing
mov rax, 0
ret
; Koppels, deel 2 -------------------------------------------------------------
; We beginnen met koppelspecifieke macro's.
; Geef in %1 de kop van %2.
%macro NEEM_KOP 2
mov %1, [%2 + 8]
%endmacro
; Geef in %1 het pel van %2.
%macro NEEM_PEL 2
mov %1, [%2 + 16]
%endmacro
; Nu is het tijd om echte koppels te maken.
; Ten eerste willen we de beschrijving:
DEF_DATA beschrijving_koppel
dq 0 ; overbeschrijving
dq uitvoering_koppel
dq afmeting_koppel
dq doorverwijzing_koppel
dq meteen_klaar ; opruiming
; Om een koppel op een voorwerp toe te passen,
; moeten we eerst de kop toepassen op het pel,
; en het resultaat daarvan uitvoeren.
DEF_CODE uitvoering_koppel
; Bewaar het voorwerp op de stapel.
BEGIN_BEWAREN
push rax
EIND_BEWAREN
push .koppel_is_toegepast
; Pas de kop toe op het pel.
NEEM_PEL rax, rsi
NEEM_KOP rsi, rsi
PAS_TOE
; Het resultaat staat in rax, en het voorwerp op de stapel.
; We zijn dus klaar om ze op elkaar aan te roepen.
.koppel_is_toegepast:
mov rsi, rax ; We willen het resultaat aanroepen
mov rax, [rsp + 8] ; op het voorwerp op de stapel.
VERGEET ; We zijn klaar met deze code.
PAS_TOE
; Een koppel heeft een beschrijving en twee wijzers, dus is 3*8 bytes lang.
DEF_CODE afmeting_koppel
mov rax, 24
ret
; Doorverwijzen is best makkelijk: geef gewoon de kop en het pel.
DEF_CODE doorverwijzing_koppel
pop rcx
add rsi, 8
push rsi
add rsi, 8
push rsi
mov rax, 2
jmp rcx
; Reken het koppel uit dat in rax staat en zet het in rax.
; Dit is dus in principe pas (kop rax) toe op (pel rax).
; Sloopt rcx en rsi.
DEF_CODE stap
NEEM_KOP rsi, rax
NEEM_PEL rax, rax
PAS_TOE
%macro STAP 0
jmp stap
%endmacro
; Nu kunnen we macro's maken om (alleen-lezen)-koppels te definiëren.
; Definieer een koppel met naam %1, kop %2 en pel %3.
%macro MAAK_KOPPEL 3
DEF_VAST %1
dq beschrijving_koppel
dq %2
dq %3
%endmacro
; Definieer een lijst met naam %1, en inhoud %2.
%macro MAAK_LIJST 2
MAAK_KOPPEL %1, %2, niks
%endmacro
; Definieer een lijst met naam %1, en inhoud %2, %3.
%macro MAAK_LIJST 3
MAAK_KOPPEL %%deel_2, %3, niks
MAAK_KOPPEL %1, %2, %%deel_2
%endmacro
; Definieer een lijst met naam %1, en inhoud %2, %3, %4.
%macro MAAK_LIJST 4
MAAK_KOPPEL %%deel_3, %4, niks
MAAK_KOPPEL %%deel_2, %3, %%deel_3
MAAK_KOPPEL %1, %2, %%deel_2
%endmacro
; Strengen --------------------------------------------------------------------
; De C-taal ten spijt is het toch fijner als strengen eruit zien als een lengte
; plus een rijtje karakters.
; Dat is dus ook hoe ze voorkomen in deze Lisp.
; Strengen hebben een simpele beschrijving:
DEF_DATA beschrijving_streng
dq 0 ; overbeschrijving
dq ongeldig ; uitvoering
dq afmeting_streng ; staat verderop ivm benodigde macro's.
dq geen_doorverwijzing
dq meteen_klaar ; opruiming
; Definieer een streng met naam %1, lengte %2 en karakters %3.
%macro MAAK_STRENG 3
DEF_VAST %1
dq beschrijving_streng
dq %2
db %3
%endmacro
; Geef in %1 de lengte van %2.
%macro NEEM_LENGTE 2
mov %1, [%2 + 8]
%endmacro
; Geef in %1 het adres van de karakters van %2.
%macro NEEM_KARAKTERS 2
lea %1, [%2 + 16]
%endmacro
DEF_CODE afmeting_streng
NEEM_LENGTE rax, rsi
add rax, 16 ; We hebben een beschrijving en de lengte zelf.
ret
; Omdat MAAK_STRENG nog niet gedefinieerd was hierboven,
; moet ongeldig_fouttekst nu gedefinieerd worden.
MAAK_STRENG ongeldig_fouttekst, 27, `Deze operatie is ongeldig!\n`
; Getallen --------------------------------------------------------------------
; Zodat het geheugen er een beetje goed uitziet,
; zetten we getallen in een object in plaats van direct in geheugen.
; Een getal in geheugen ziet eruit als | beschrijving | waarde |.
DEF_DATA beschrijving_getal
dq 0 ; overbeschrijving
dq ongeldig ; uitvoering
dq afmeting_getal
dq geen_doorverwijzing
dq meteen_klaar ; opruiming
; Een getal heeft een beschrijving en waarde.
DEF_CODE afmeting_getal
mov rax, 16
ret
; Definieer een streng met naam %1 en waarde %2.
%macro MAAK_GETAL 2
DEF_VAST %1
dq beschrijving_getal
dq %2
%endmacro
; Geef in %1 de waarde van het getal in %2.
%macro NEEM_WAARDE 2
mov %1, [%2 + 8]
%endmacro
; Omgevingen ------------------------------------------------------------------
; We willen kunnen werken met variabelen die in een of andere omgeving staan.
; In `_start' wordt een omgeving in rbx gestopt,
; op basis van de definities die gebruik maken van de komende macro's.
;
; Een omgeving ziet eruit als een lijst van lemmata: koppels (naam . waarde),
; met naam een streng.
; Deze vorm is makkelijk doorzoekbaar en niet te onvriendelijk te definiëren
; De buitenomgeving is de omgeving die we gebruiken bij het opstarten.
; Zet een kop op de buitenomgeving om een nieuwe waarde beschikbaar te maken.
%define BUITENOMGEVING niks
; Stop een waarde in de buitenomgeving.
; %1 is de naam,
; %2 is de waarde.
%macro ZET_BUITEN 2
%strlen naamlengte %1
MAAK_STRENG %%naam, naamlengte, %1
MAAK_KOPPEL %%lemma, %%naam, %2
MAAK_KOPPEL %%omgeving, %%lemma, BUITENOMGEVING
%define BUITENOMGEVING %%omgeving
%endmacro
; Variabelen ------------------------------------------------------------------
; Variabelen worden hier weergegeven als adressen.
; Om een variabele uit te lezen, moet je dus de waarde op het adres uitlezen.
; Definieer een variabele met Lispnaam %1 en adres %2.
%macro VARIABELE 2
MAAK_GETAL %%var, %2
ZET_BUITEN %1, %%var
%endmacro
; Opstarten -------------------------------------------------------------------
; We hebben gelukkig niet heel veel nodig om op te starten:
; we moeten een omgeving hebben en een stuk geheugen om op te werken.
; Wijst naar de eerste byte van vrij bewerkbare ruimte.
; Wordt door _start geïnitialiseerd.
DEF_DATA begin_vrije_ruimte
dq 0
VARIABELE "begin-vrije-ruimte", begin_vrije_ruimte
; Wijst naar de eerste ongebruikte byte van vrij bewerkbare ruimte.
; Wordt door _start geïnitialiseerd.
DEF_DATA vrije_ruimte
dq 0
VARIABELE "vrije-ruimte", vrije_ruimte
; Wijst naar het einde van vrij bewerkbare ruimte.
; Wordt door _start geïnitialiseerd.
DEF_DATA einde_vrije_ruimte
dq 0
VARIABELE "einde-vrije-ruimte", einde_vrije_ruimte
; Wijst naar de eerste byte van het alternatieve stuk geheugen.
; Wordt door _start geïnitialiseerd.
DEF_DATA begin_levende_ruimte
dq 0
VARIABELE "begin-levende-ruimte", begin_levende_ruimte
; Wijst naar de eerste ongebruikte byte van het alternatieve stuk geheugen.
; Wordt door _start geïnitialiseerd.
DEF_DATA levende_ruimte
dq 0
VARIABELE "levende-ruimte", levende_ruimte
; Wijst naar het einde van het alternatieve geheugen.
; Wordt door _start geïnitialiseerd.
DEF_DATA einde_levende_ruimte
dq 0
VARIABELE "einde-levende-ruimte", einde_levende_ruimte
; Zorg ervoor dat we geheugen hebben om onze koppels in te zetten.
; De bedoeling is dat dit helemaal aan het begin van uitvoering
; aangeroepen wordt (mbv call).
; Sloopt o.a. %rax en %rbx.
;
; Gebaseerd op code overgenomen uit Jones Forth.
%define INITIAL_DATA_SEGMENT_SIZE 0x1000000
DEF_CODE set_up_data_segment
mov rdi, 0
mov rax, syscall_brk
syscall
mov [begin_vrije_ruimte], rax
mov [vrije_ruimte], rax
mov rdi, rax
add rdi, INITIAL_DATA_SEGMENT_SIZE
mov rax, syscall_brk
syscall
mov [einde_vrije_ruimte], rax
mov [begin_levende_ruimte], rax
mov [levende_ruimte], rax
add rdi, INITIAL_DATA_SEGMENT_SIZE
mov rax, syscall_brk
syscall
mov [einde_levende_ruimte], rax
; Eventueel willen we hier controleren dat we daadwerkelijk ruimte hebben?
ret
; Data in elkaar zetten -------------------------------------------------------
; Tot nu toe konden we al wel data in de interpreter neerzetten,
; maar nog niet in de code maken.
; De volgende code lost dat op.
; Maak een nieuw koppel (%1 . %2).
; Uitkomst komt in rax.
; Sloopt r8: %1 en %2 mogen dat dus niet zijn.
; %1 en %2 mogen wel rax en [rsp] zijn(!)
%macro KOPPEL 2
; Haal de voorwerpen uit de weg: eerst naar registers
; en dan op de stapel (niet direct, want dan werkt [rsp] niet meer.)
mov r8, %1
mov rax, %2
push rax
push r8
; Zet het koppel in elkaar.
mov rax, [vrije_ruimte]
mov qword [rax], beschrijving_koppel
pop qword [rax + 8]
pop qword [rax + 16]
; Werk de vrije ruimte bij.
mov r8, rax
add r8, 24
mov [vrije_ruimte], r8
%endmacro
; Maak een nieuwe streng van lengte %1.
; Uitkomst komt in rax.
; %1 mag rax zijn(!)
; Hierna kun je de karakters naar [vrije_ruimte] kopiëren.
%macro STRENG 1
; Zie ook de inhoud van KOPPEL.
mov rax, %1
push rcx
mov rcx, rax
; Zet de streng in elkaar.
mov rax, [vrije_ruimte]
mov qword [rax], beschrijving_streng
mov [rax + 8], rcx
; Werk de vrije ruimte bij:
; rcx is de lengte van de streng zelf
add rcx, 8 ; plus de ruimte nodig voor de lengte
add rcx, rax ; plus [vrije_ruimte] is de nieuwe waarde
mov [vrije_ruimte], rcx
pop rcx
%endmacro
; Maak een nieuw getal met waarde %1.
; Uitkomst komt in rax.
; %1 mag rax zijn(!)
%macro GETAL 1
; Zie ook de inhoud van KOPPEL.
mov rax, %1
push rcx
mov rcx, rax
mov rax, [vrije_ruimte]
mov qword [rax], beschrijving_getal
mov [rax + 8], rcx
add qword [vrije_ruimte], 16
pop rcx
%endmacro
; Ingebakken functies ---------------------------------------------------------
; Nu zijn we klaar om functies te definiëren die in Lisp aan te roepen zijn.
; Een functie geven we weer als | beschrijving (8) | code (...) |
DEF_DATA beschrijving_functie
dq 0 ; overbeschrijving
dq uitvoering_functie
dq geen_afmeting
dq geen_doorverwijzing
dq meteen_klaar ; opruiming
DEF_CODE uitvoering_functie
add rsi, 8
jmp rsi
; Begin van een functie die voorwerpen niet uitrekent.
%macro FN 1
DEF_CODE %1
dq beschrijving_functie
.code:
%endmacro
; Begin van een functie die 1 voorwerp uitrekent.
; Hierna staat:
; voorwerp 1 in rax
%macro FN_VOORWERPEN_1 1
FN %1
; Eventueel willen we controleren op de juiste voorwerpvorm?
NEEM_KOP rax, rax
BEWAAR_NIETS
; reken uit en kom terug
push .klaar_1
STAP
.klaar_1:
VERGEET
%endmacro
; Begin van een functie die 2 voorwerpen uitrekent.
; Hierna staat:
; voorwerp 1 in rcx
; voorwerp 2 in rax
%macro FN_VOORWERPEN_2 1
FN %1
; Eventueel willen we controleren op de juiste voorwerpvorm?
; Splits voorwerpen op.
NEEM_PEL rcx, rax
NEEM_KOP rax, rax
; Het pel (rcx) gaat op de stapel en de kop (rax) wordt uitgerekend.
BEGIN_BEWAREN
push rcx
EIND_BEWAREN
push .klaar_1
STAP
.klaar_1:
; Nu is rax de waarde van de kop en [rsp+8] een lijst uit te rekenen waarden.
; We hadden al wat ruimte op de stapel, wissel dat in
mov rcx, [rsp+8]
mov [rsp+8], rax
NEEM_KOP rax, rcx
push .klaar_2
STAP
.klaar_2:
; Onthoud voorwerp 1.
mov rcx, [rsp+8]
VERGEET
%endmacro
; Begin van een functie die 3 voorwerpen uitrekent.
; Hierna staat:
; voorwerp 1 in rdx
; voorwerp 2 in rcx
; voorwerp 3 in rax
%macro FN_VOORWERPEN_3 1
FN %1
; Eventueel willen we controleren op de juiste voorwerpvorm?
; Splits voorwerpen op: 1 -> rax, 2 -> rcx, 3 -> rdx
NEEM_PEL rcx, rax
NEEM_KOP rax, rax
NEEM_PEL rdx, rcx
NEEM_KOP rcx, rcx
NEEM_KOP rdx, rdx
; Bewaar voorwerpen, 3 onder 2.
BEGIN_BEWAREN
push rdx
push rcx
EIND_BEWAREN
push .klaar_1
STAP
.klaar_1:
; Nu is rax de waarde van voorwerp 1 en [rsp+8], [rsp+16] uitdrukkingen 2 + 3
; Wissel 1 en 2 om en reken 2 uit.
mov rcx, [rsp+8]
mov [rsp+8], rax
mov rax, rcx
push .klaar_2
STAP
.klaar_2:
; Wissel 2 en 3 om en reken 3 uit.
mov rcx, [rsp+16]
mov [rsp+16], rax
mov rax, rcx
push .klaar_3
STAP
.klaar_3:
; Stop alles weer in registers:
; 1 -> rdx
; 2 -> rcx
; 3 -> rax
mov rcx, [rsp+16]
mov rdx, [rsp+8]
VERGEET
%endmacro
; Lispfunctie: reken voorwerp 1 en 2 uit en koppel de uitkomsten.
FN_VOORWERPEN_2 koppel
; Nu is rcx de waarde van de kop en rax van het pel
KOPPEL rcx, rax
ret
ZET_BUITEN "koppel", koppel
; Lispfunctie: reken voorwerp 1 uit en neem de kop ervan.
FN_VOORWERPEN_1 kop
; Controleer of dit wel pelneembaar is.
cmp qword [rax], beschrijving_koppel
je .neem_kop
FOUTMELDING fout_kop
.neem_kop:
NEEM_KOP rax, rax
ret
MAAK_STRENG fout_kop, 37, `Daar kan ik toch geen kop van nemen!\n`
ZET_BUITEN "kop", kop
; Lispfunctie: reken voorwerp 1 uit en neem het pel ervan.
FN_VOORWERPEN_1 pel
; Controleer of dit wel pelneembaar is.
cmp qword [rax], beschrijving_koppel
je .neem_pel
BEGIN_BEWAREN
push rax
EIND_BEWAREN
FOUTMELDING fout_pel
.neem_pel:
NEEM_PEL rax, rax
ret
MAAK_STRENG fout_pel, 37, `Daar kan ik toch geen pel van nemen!\n`
ZET_BUITEN "pel", pel
; Lispfunctie: geef voorwerp 1 terug zonder verder te rekenen.
FN gewoon
; Eventueel willen we controleren op de juiste voorwerpvorm?
NEEM_KOP rax, rax
ret
ZET_BUITEN "gewoon", gewoon
; Lispfunctie: reken het voorwerp uit en voer de code erin uit.
FN_VOORWERPEN_1 voer_uit
; De uit te voeren code staat in %rax, dus we kunnen gewoon een stap zetten.
STAP
ZET_BUITEN "voer-uit", voer_uit
; Lispfunctie: blijf steeds het voorwerp uitrekenen.
FN steeds
NEEM_KOP rax, rax
BEGIN_BEWAREN
push rax
EIND_BEWAREN
.herhaald:
mov rax, [rsp + 8]
push .herhaald
STAP
ZET_BUITEN "steeds", steeds
; Lispfunctie: beslis of voorwerp 1 niet `niks' is,
; voer voorwerp 2 uit indien iets,
; en voorwerp 3 indien niks.
FN beslis
BEGIN_BEWAREN
NEEM_KOP rcx, rax
NEEM_PEL rax, rax
NEEM_KOP rdx, rax
push rdx
NEEM_PEL rax, rax
NEEM_KOP rdx, rax
push rdx
EIND_BEWAREN
mov rax, rcx
push .geval_uitgerekend
STAP
.geval_uitgerekend:
; hier is rax de waarde van het geval,
; [ rsp + 8 ] code bij niks
; en [ rsp + 16 ] code bij iets
cmp rax, niks
je .is_niks
mov rax, [rsp + 16]
VERGEET
STAP
.is_niks:
mov rax, [rsp + 8]
VERGEET
STAP
ZET_BUITEN "beslis", beslis
; Lispfunctie: voer alle voorwerpen 1 voor 1 uit.
; Geeft het laatste resultaat.
; (Hebben we geen voorwerpen gekregen, is het resultaat `niks'.)
FN doe
; rax is een lijst van uit te voeren voorwerpen
; die komen ook in [rsp+8]
BEGIN_BEWAREN
push rax
EIND_BEWAREN
.doe_meer:
; Ga door tot we niks hebben.
mov rcx, [rsp+8]
cmp rcx, niks
je .doe_niks_meer
NEEM_PEL rax, rcx
mov [rsp+8], rax
NEEM_KOP rax, rcx
push .doe_meer
STAP
.doe_niks_meer:
VERGEET
ret
ZET_BUITEN "doe", doe
; Lispfunctie: wordt aangeroepen als de voortzetting leeg is.
FN klaar
STOP rax
ZET_BUITEN "klaar", klaar
; Lispfunctie: geef het voorwerp terug, maar gdb stopt hierop.
FN_VOORWERPEN_1 kever
ret
ZET_BUITEN "kever", kever
; Rekenen ---------------------------------------------------------------------
; Nu komen wat basale operaties op getallen.
; Ze hebben allemaal dezelfde structuur: lees de waarde van wat parameters uit,
; en stop een nieuw getal in %rax.
; Vandaar het volgende macro:
; Definieer een functie genaamd %1,
; die een voorwerp uitrekent
; en de instructie %2 op de getalswaarde (in rax) doet.
; Resultaat is het getal met getalswaarde rax.
%macro FN_GETAL_1 2
FN_VOORWERPEN_1 %1
NEEM_WAARDE rax, rax
%2
GETAL rax
ret
%endmacro
; Definieer een functie genaamd %2,
; die twee voorwerpen uitrekent
; en de instructie %2 op de getalswaarde (in rax en rdx resp) doet.
; Resultaat is het getal met getalswaarde rax.
; De reden voor rax en rdx is dat dit ook de volgorde is van idiv.
%macro FN_GETAL_2 2
FN_VOORWERPEN_2 %1
NEEM_WAARDE rdx, rax
NEEM_WAARDE rax, rcx
%2
GETAL rax
ret
%endmacro
; Hoog voorwerp op met 1
FN_GETAL_1 incr, {inc rax}
ZET_BUITEN "+1", incr
; Verlaag voorwerp met 1
FN_GETAL_1 decr, {dec rax}
ZET_BUITEN "-1", decr
; Tel twee voorwerpen bij elkaar op
FN_GETAL_2 plus, {add rax, rdx}
ZET_BUITEN "+", plus
; Trek voorwerp 2 af van voorwerp 1
FN_GETAL_2 min, {sub rax, rdx}
ZET_BUITEN "-", min
; Vermenigvuldig twee voorwerpen.
; We negeren het geval dat de uitkomst niet in 64 bits past.
FN_GETAL_2 keer, {imul rax, rdx}
ZET_BUITEN "*", keer
; Deel voorwerp 1 door voorwerp 2 en geef een koppel (quotiënt . rest).
FN_VOORWERPEN_2 deelmod
xchg rax, rcx ; Wissel de voorwerpen om ivm de volgorde van idiv
NEEM_WAARDE rax, rax
NEEM_WAARDE rcx, rcx
cqo ; We willen rax delen op rcx, maar idiv doet rdx:rax. Signextend dus.
idiv rcx
GETAL rax
mov rcx, rax
GETAL rdx
KOPPEL rcx, rax
ret
ZET_BUITEN "/%", deelmod
; We moeten ook getallen kunnen invoeren in de code.
; Om verwarring met namen te voorkomen, schrijf je een getal op als
; ( getal 37 ) (dus niet `37' letterlijk).
; Dit maakt de uitdrukkinglezer ook een stukje eenvoudiger.
FN getal
; rax is ((var . getalnaam . niks) . niks), maak daar `getalnaam' van.
NEEM_KOP rax, rax
NEEM_PEL rax, rax
NEEM_KOP rax, rax
; rcx wordt de (overgebleven) lengte, rsi de karakters en rdx het resultaat.
NEEM_LENGTE rcx, rax
NEEM_KARAKTERS rsi, rax
mov rax, 0 ; zet op 0 zodat lezen naar al en optellen met rax goed gaat
mov rdx, 0
; TEDOEN: negatieve getallen
.lees_volgend_cijfer:
cmp rcx, 0
jle .getal_gelezen
imul rdx, 10 ; er komt een extra cijfer in het resultaat
lodsb ; lees 1 byte uit [rsi] naar al en hoog rsi op
sub al, '0' ; gelukkig zijn cijfers opeenvolgend in ASCII
add rdx, rax ; we hebben de bitjes die buiten al vallen al op 0 gezet
dec rcx
jmp .lees_volgend_cijfer
.getal_gelezen:
; rdx bevat het resultaat, stop dat in een object
GETAL rdx
ret
ZET_BUITEN "getal", getal
; Lispwaarde: generieke waarde die niet `niks' is.
DEF_UNIEK iets
ZET_BUITEN "iets", iets
; Lispfunctie: beslis of twee getallen gelijk zijn.
; Zo ja, geeft `iets', zo nee, geeft `niks'.
FN_VOORWERPEN_2 gelijkheid_getallen
; Hier is rcx het ene getal-object en rax het andere.
NEEM_WAARDE rcx, rcx
NEEM_WAARDE rax, rax
cmp rax, rcx
jne .geef_niks
mov rax, iets
ret
.geef_niks:
mov rax, niks
ret
ZET_BUITEN "=", gelijkheid_getallen
; Lispfunctie: beslis of twee getallen strikt ongelijk zijn.
; Zo ja, geeft `iets', zo nee, geeft `niks'.
FN_VOORWERPEN_2 ongelijkheid_getallen
; Hier is rcx het ene getal-object en rax het andere.
NEEM_WAARDE rcx, rcx
NEEM_WAARDE rax, rax
cmp rcx, rax
jge .geef_niks
mov rax, iets
ret
.geef_niks:
mov rax, niks
ret
ZET_BUITEN "<", ongelijkheid_getallen
; Werken met geheugen ---------------------------------------------------------
; We hebben ook wat functies om redelijk direct met geheugen om te gaan.
; Geheugenadressen worden weergegeven als getallen.
; Lispfunctie: geef de waarde achter het adres als 64-bits getal.
FN_VOORWERPEN_1 geef_uit_adres
; hier is rax een getal met het adres.
NEEM_WAARDE rax, rax
GETAL [rax]
ret
ZET_BUITEN "geef-uit-adres", geef_uit_adres
; Lispfunctie: geef het object waar het adres naar wijst.
FN_VOORWERPEN_1 geef_object_uit_adres
; hier is rax een getal met het adres.
NEEM_WAARDE rax, rax
ret
ZET_BUITEN "adres→object", geef_object_uit_adres
; Lispfunctie: zet op het adres de gegeven waarde (als 64-bits getal).
FN_VOORWERPEN_2 zet_op_adres
; hier is rcx een getal met het adres en rax een getal met de waarde.
NEEM_WAARDE rcx, rcx
NEEM_WAARDE rax, rax
mov [rcx], rax
ret
ZET_BUITEN "zet-op-adres", zet_op_adres
; Lispfunctie: kopieer een losse byte naar het doeladres van het bronadres.
FN_VOORWERPEN_2 kopieer_byte
; hier is rax een getal met het bronadres en rcx een getal met het doeladres.
NEEM_WAARDE rax, rax
NEEM_WAARDE rcx, rcx
mov al, byte [rax]
mov byte [rcx], al
ret
ZET_BUITEN "kopieer-byte", kopieer_byte
; Lispfunctie: reserveer de gegeven hoeveelheid bytes aan vrij geheugen.
FN_VOORWERPEN_1 reserveer
NEEM_WAARDE rax, rax
mov rcx, [vrije_ruimte]
add rax, rcx
; hier is rax het einde van de gereserveerde ruimte en rcx het begin
mov [vrije_ruimte], rax
GETAL rcx
ret
ZET_BUITEN "reserveer", reserveer
; Lispfunctie: geef een getal met het adres van het voorwerp.
; (Let op dat dit adres misschien niet meer klopt als vuilnis opgeruimd wordt!)
; Zo geeft bijvoorbeeld ( @ ( adres object ) ) de beschrijving van een object.
FN_VOORWERPEN_1 adres
GETAL rax
ret
ZET_BUITEN "adres", adres
; Lispfunctie: beslis of twee stukken geheugen gelijk zijn.
; Voorwerpen: lengte, wijzer 1, wijzer 2.
; Zo ja, geeft `iets', zo nee, geeft `niks'.
FN_VOORWERPEN_3 gelijkheid_geheugen
; Hier is rax wijzer 2, rcx wijzer 1 en rdx lengte.
NEEM_WAARDE rsi, rax
NEEM_WAARDE rdi, rcx
NEEM_WAARDE rcx, rdx
; vergelijk rdi en rsi over rcx bytes.
repe cmpsb
jne .geef_niks
mov rax, iets
ret
.geef_niks:
mov rax, niks
ret
ZET_BUITEN "gelijkheid-geheugen", gelijkheid_geheugen
; Vuilnis opruimen ------------------------------------------------------------
; Het uitvoeren van al deze code gebruikt geheugen,
; en we hebben geen zin om al het geheugengebruik handmatig aan te pakken.
; We ruimen dit op met een dubbele buffer:
; Om de zoveel tijd verhuizen we alle bestaande waarden
; naar een ander stuk geheugen.
; Dat wordt dan de plek van de vrije ruimte.
; Na verloop van tijd raakt die vol, en doen we de omgekeerde verhuizing.
; TEDOEN: fix dat functies niet netjes worden verhuisd