-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathklein-tiny.lisp
More file actions
executable file
·279 lines (244 loc) · 10.9 KB
/
Copy pathklein-tiny.lisp
File metadata and controls
executable file
·279 lines (244 loc) · 10.9 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
;;; klein-tiny.lisp
;;; TM (Tiny Machine) code generator for Klein
;;; Chuck Hoffman
;;; Translation of Programming Languages
;;; Fall 2007
;; give some nice convenient names to some registers & offset values
(defparameter left 0) ; register 0 for left operand, or single things
(defparameter right 1) ; register 1 for right operand
(defparameter result 2) ; register 2 for result
(defparameter misc1 4) ; registers 3 and 4 for misc
(defparameter misc2 3) ; (address manipulations mostly)
(defparameter sp 5) ; register 5 for stack pointer
(defparameter fp 6) ; register 6 for frame pointer
(defparameter pc 7) ; register 7 is program counter, as we know
(defparameter returnval 0) ; fp + returnval is where return value goes
(defparameter returnaddr 1) ; fp + returnaddr is return-to address
(defparameter savefp 2) ; fp + savefp is old fp
(defparameter args 3) ; args start at fp + args
(defparameter top 0) ; sp + top is top of temp stack
(defparameter under -1) ; sp + under is 2nd item on temp stack
;; generate a tiny instruction
(defun tiny-instr (opcode r s u &optional (comment ""))
(list opcode r s u comment))
;; tiny instructions to push a temp to stack
(defun push-temp (register)
(list (tiny-instr 'lda sp 1 sp "push-temp: increment sp")
(tiny-instr 'st register 0 sp "store at sp")))
;; tiny instructions to pop a temp from stack into a register
(defun pop-temp (register)
(list (tiny-instr 'ld register top sp "pop-temp: load")
(tiny-instr 'lda sp -1 sp "decrement sp")))
;; tiny instructions to pop two temps from stack at once, with the top
;; temp to be considered the right operand and the one under it the left
(defun pop-temp-lr ()
(list (tiny-instr 'ld right top sp "pop-temp-lr: load right")
(tiny-instr 'ld left under sp "pop-temp-lr: load left")
(tiny-instr 'lda sp -2 sp "decrement sp by 2")))
(defmethod generate ((node ast-literalint))
(cons (tiny-instr 'ldc result (value node) 0 "int literal")
(push-temp result)))
(defmethod generate ((node ast-literalboolt))
(declare (ignore node))
(cons (tiny-instr 'ldc result 1 0 "literal true")
(push-temp result)))
(defmethod generate ((node ast-literalboolf))
(declare (ignore node))
(cons (tiny-instr 'ldc result 0 0 "literal false")
(push-temp result)))
(defmethod operate ((node ast-plusop))
(list (tiny-instr 'add result left right "+")))
(defmethod operate ((node ast-minusop))
(list (tiny-instr 'sub result left right "-")))
(defmethod operate ((node ast-timesop))
(list (tiny-instr 'mul result left right "*")))
(defmethod operate ((node ast-divideop))
(list (tiny-instr 'div result left right "/")))
(defmethod operate ((node ast-lessop))
(list (tiny-instr 'sub left left right "<: subtract right from left")
(tiny-instr 'jlt left 2 pc "result < 0 if left < right")
(tiny-instr 'ldc result 0 0 "false")
(tiny-instr 'lda pc 1 pc "jump over")
(tiny-instr 'ldc result 1 0 "true")))
(defmethod operate ((node ast-equalsop))
(list (tiny-instr 'sub left left right "=: subtract right from left")
(tiny-instr 'jeq left 2 pc "result = 0 if left = right")
(tiny-instr 'ldc result 0 0 "false")
(tiny-instr 'lda pc 1 pc "jump over")
(tiny-instr 'ldc result 1 0 "false")))
(defmethod generate ((node ast-orop))
(let* ((right-part
(append
(generate (right node))
(pop-temp right)
(list
(tiny-instr 'jeq right 2 pc "if right = 0 skip")
(tiny-instr 'ldc result 1 0 "result = true")
(tiny-instr 'lda pc 1 pc "skip")
(tiny-instr 'ldc result 0 0 "result = false"))))
(left-part
(append
(generate (left node))
(pop-temp left)
(list
(tiny-instr 'jeq left 2 pc "or: if left = 0 jump to r")
(tiny-instr 'ldc result 1 0 "result = true")
(tiny-instr 'lda pc (length right-part) pc "short-circuit")))))
(append left-part right-part (push-temp result))))
(defmethod operate ((node ast-notop))
(list (tiny-instr 'jne left 2 pc "not: jump if not 0")
(tiny-instr 'ldc result 1 0 "result = true")
(tiny-instr 'lda pc 1 pc "jump over")
(tiny-instr 'ldc result 0 0 "result = false")))
(defmethod generate ((node ast-binaryop))
(append (generate (left node))
(generate (right node))
(pop-temp-lr)
(operate node)
(push-temp result)))
(defmethod generate ((node ast-unaryop))
(append (generate (operand node))
(pop-temp left)
(operate node)
(push-temp result)))
(defmethod generate ((node ast-ifexpr))
(let ((truepart (generate (if-part node)))
(falsepart (generate (else-part node))))
(append (generate (test-expr node))
(pop-temp left)
(cons
(tiny-instr 'jeq left (1+ (length truepart)) pc "skip true case")
truepart)
(cons
(tiny-instr 'lda pc (length falsepart) pc "skip false case")
falsepart))))
;; an argument is passed by evaluating the expression and just leaving it
;; on the stack
(defmethod generate ((node ast-actual))
(generate (expr node)))
(defun append-code-chunks (list-of-chunks)
(if (null list-of-chunks) nil
(append (car list-of-chunks)
(append-code-chunks (cdr list-of-chunks)))))
(defmethod generate ((node ast-actualseq))
(append-code-chunks (mapcar #'generate (seq node))))
;; the following two leave untranslated identifiers in the generated code
;; (as their string names). These must be looked up in a symbol table and
;; changed to their index numbers after generation:
(defmethod generate ((node ast-call))
(let ((func-name (name (function-identifier node))))
(append
; stack frame (fp) for new call will start at current sp + 1
(list (tiny-instr 'ldc result 0 0 "setup new stack frame")
(tiny-instr 'st result (1+ returnval) sp "return value")
(tiny-instr 'st fp (1+ savefp) sp "save current fp")
(tiny-instr 'lda sp args sp "set sp to push args"))
(generate (arg-seq node))
(list (tiny-instr 'lda fp (- (+ 2 (get-arity node))) sp "compute new fp")
(tiny-instr 'lda misc1 2 pc "compute return address")
(tiny-instr 'st misc1 returnaddr fp "store it")
(tiny-instr 'ldc pc func-name 0 func-name)))))
;; for variable identifiers only -- generate ast-call does not use this
(defmethod generate ((node ast-identifier))
(cons (tiny-instr 'ld result (name node) fp (name node))
(push-temp result)))
;; looks for identifier-strings in second-operand position (presuming they name
;; local variables) and replaces them with their offset from frame pointer
;; (lexical index plus start-of-args)
(defun patch-variables (instruction symbol-table)
(tiny-instr (first instruction)
(second instruction)
(if (stringp (third instruction))
(let ((lexaddr
(st-lookup-lexaddr (third instruction) symbol-table)))
(if (and lexaddr (= 0 (first lexaddr)))
(+ args (second lexaddr))
(third instruction)))
(third instruction))
(fourth instruction)
(fifth instruction)))
;; looks for identifier-strings in the second-operand position (presuming they
;; are function names being loaded as constants into pc) and replaces them
;; with their addresses (as computed earlier and given in the args here)
(defun patch-function-names (instruction functable)
(tiny-instr (first instruction)
(second instruction)
(if (stringp (third instruction))
; jump 1 short because of pc increment
(1- (gethash (third instruction) functable))
(third instruction))
(fourth instruction)
(fifth instruction)))
;; The result value of a call ends up just being left on top of the temp stack
;; of the caller.
(defmethod generate ((node ast-definition))
(append
(mapcar
#'(lambda (instr) (patch-variables instr (symbol-table node)))
(generate (body node)))
(pop-temp result) ; get value of body from stack
(list (tiny-instr 'st result returnval fp "store return-value")
(tiny-instr 'lda sp returnval fp "point sp to it")
(tiny-instr 'ld misc1 returnaddr fp "fetch return address")
(tiny-instr 'ld fp savefp fp "restore caller's fp")
(tiny-instr 'lda pc 0 misc1 "jump to return addr"))))
(defun function-start-addrs (func-list first-addr)
(labels
((starts
(lengths start-addr)
(if (null lengths) nil ;(list start)
(cons start-addr
(starts (cdr lengths) (+ (car lengths) start-addr))))))
(starts (mapcar #'length func-list) first-addr)))
(defun generate-mainargs-push (reg arity &optional (argnum 0))
(if (= argnum arity) nil
(append
(list (tiny-instr 'ld result argnum reg "command-line arg")
(tiny-instr 'st result (+ argnum args) fp))
(generate-mainargs-push reg arity (1+ argnum)))))
(defmethod generate ((node ast-program))
(let* ((func-names (get-function-names node))
(functions (mapcar #'generate (seq (definition-seq node))))
(main-arity (get-arity (get-main-function node)))
(prelude
(append
(list (tiny-instr 'ldc fp (1+ main-arity) 0 "first avail. addr")
(tiny-instr 'ldc misc1 0 0)
(tiny-instr 'st misc1 returnval fp "first stack frame")
(tiny-instr 'st fp savefp fp)
(tiny-instr 'lda sp (1- args) fp "start stack")
(tiny-instr 'ldc misc2 1 0 "addr of cmd-args"))
(generate-mainargs-push misc2 main-arity)
(list (tiny-instr 'lda sp main-arity sp "update sp")
(tiny-instr 'lda misc1 2 pc "compute return addr")
(tiny-instr 'st misc1 returnaddr fp "store it")
(tiny-instr 'ldc pc "main" 0 "jump to main")
(tiny-instr 'ld result returnval fp "get return value")
(tiny-instr 'out result 0 0 "print it")
(tiny-instr 'halt 0 0 0))))
(func-addrs (function-start-addrs functions (1+ (length prelude))))
(functable (make-hash-table :test #'equal)))
(mapcar
#'(lambda (name addr) (setf (gethash name functable) addr))
func-names func-addrs)
(mapcar
#'(lambda (instr) (patch-function-names instr functable))
(append prelude (append-code-chunks functions)))))
(defun print-code (code &optional (dest t))
(dotimes (lineno (length code))
(let ((instr (elt code lineno)))
(format dest
(if (find (first instr)
(list 'halt 'in 'out 'add 'sub 'mul 'div))
"~3D: ~5S ~D,~D,~D~40T* ~A~%"
"~3D: ~5s ~D,~D(~D)~40T* ~A~%")
lineno
(first instr)
(second instr)
(third instr)
(fourth instr)
(fifth instr)))))
;; TODO:
;; fix print parsing
;; add (defun generate ((node ast-printop)) ..)
;; optimizations? redundant load, store-load cycles, etc