@@ -56,11 +56,89 @@ a function value.
5656We will end up eliminating them in future versions of the compiler;
5757they are simply a crutch for now.
5858
59-
6059@section[#:tag-prefix "knock " ]{A Compiler with Function pointers}
6160
61+ The main idea in making functions into values is we will need to have
62+ a representation of functions. We will use a representation similar
63+ to boxes: functions will be heap allocated data structures. What will
64+ be stored in the heap? The address of the label of the function.
65+
66+ A function reference, @racket[(fun _f)], will allocate a 64-bit
67+ segment of the heap, store the location of the function's label,
68+ i.e. a pointer to the instructions for the function, and tag the
69+ pointer as a ``procedure value,'' which is new , disjoint kind of
70+ value.
71+
72+ @#reader scribble/comment-reader
73+ (racketblock
74+ ;; Variable -> Asm
75+ (define (compile-fun f)
76+ `(; rax <- address of label f
77+ (lea rax (offset ,(symbol->label f) 0 ))
78+ ; write in to heap
79+ (mov (offset rdi 0 ) rax)
80+ ; rax <- pointer into heap
81+ (mov rax rdi)
82+ ; tag as procedure pointer
83+ (or rax ,type-proc)
84+ ; alloc
85+ (add rdi 8 )))
86+ )
87+
88+ A function call, @racket[(call _e0 _es ... )] will evaluate on the
89+ subexpressions. The @racket[_e0] expression should produce a
90+ function, i.e. tagged pointer. We can erase the tag to compute the
91+ address in the heap. Dereferencing that location, gets us the label
92+ address, which can then jump to.
93+
94+ @#reader scribble/comment-reader
95+ (racketblock
96+ ;; Expr (Listof Expr) CEnv -> Asm
97+ (define (compile-fun-call e0 es c)
98+ (let ((cs (compile-es es (cons #f c)))
99+ (c0 (compile-e e0 c))
100+ (i (- (add1 (length c))))
101+ (stack-size (* 8 (length c))))
102+ `(,@c0
103+ ; save f in stack
104+ (mov (offset rsp ,i) rax)
105+ ,@cs
106+ ; restore f
107+ (mov rax (offset rsp ,i))
108+ ,@assert-proc
109+ (sub rsp ,stack-size)
110+ (xor rax ,type-proc)
111+ ; call f
112+ (call (offset rax 0 ))
113+ (add rsp ,stack-size))))
114+ )
115+
116+ A tail call version of the above can be defined as:
117+
118+ @#reader scribble/comment-reader
119+ (racketblock
120+ ;; Expr (Listof Expr) CEnv -> Asm
121+ (define (compile-fun-tail-call e0 es c)
122+ (let ((cs (compile-es es (cons #f c)))
123+ (c0 (compile-e e0 c))
124+ (i (- (add1 (length c)))))
125+ `(,@c0
126+ (mov (offset rsp ,i) rax)
127+ ,@cs
128+ (mov rax (offset rsp ,i))
129+ ,@(move-args (length es) i)
130+ ,@assert-proc
131+ (xor rax ,type-proc)
132+ (jmp (offset rax 0 )))))
133+ )
134+
135+ The complete compiler:
136+
62137@codeblock-include["knock/compile.rkt " ]
63138
139+ We can verify that the compiler works for programs that use functions
140+ like before:
141+
64142@ex[
65143(asm-interp
66144 (compile '(begin (define (f x)
@@ -70,10 +148,19 @@ they are simply a crutch for now.
70148 (call (fun f) 10 ))))
71149]
72150
151+ But it also works when functions are put in lists:
152+
153+ @ex[
154+ (asm-interp
155+ (compile '(begin (define (f x) x)
156+ (call (car (cons (fun f) '() )) 7 ))))
157+ ]
158+
159+ And functions that produce functions:
160+
73161@ex[
74162(asm-interp
75163 (compile '(begin (define (f x) (fun h))
76164 (define (h y) y)
77165 (call (call (fun f) 5 ) 9 ))))
78-
79166]
0 commit comments