Skip to content

Commit a0a13e4

Browse files
authored
Merge pull request #40 from dvanhorn/next
Next
2 parents 8b5c0eb + 9374e33 commit a0a13e4

6 files changed

Lines changed: 293 additions & 39 deletions

File tree

www/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ $(course):
1717
$(course).scrbl
1818
mkdir -p $(course)/code/
1919
cd notes ; \
20-
tar -c `git ls-files -X .gitignore intro abscond blackmail con dupe extort fraud hustle iniquity` | \
20+
tar -c `git ls-files -X .gitignore intro abscond blackmail con dupe extort fraud hustle iniquity jig knock` | \
2121
(cd ../main/code ; tar -x) ; cd ../..
2222

2323
clean:

www/notes.scrbl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
@include-section{notes/hustle.scrbl}
1717
@include-section{notes/iniquity.scrbl}
1818
@include-section{notes/jig.scrbl}
19+
@include-section{notes/knock.scrbl}
1920
@;{
2021
@include-section{notes/5.scrbl}
2122
@include-section{notes/6.scrbl}

www/notes/knock.scrbl

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
#lang scribble/manual
2+
3+
@(require (for-label (except-in racket ...)))
4+
@(require redex/pict
5+
racket/runtime-path
6+
scribble/examples
7+
"hustle/semantics.rkt"
8+
"utils.rkt"
9+
"ev.rkt"
10+
"../utils.rkt")
11+
12+
@(define codeblock-include (make-codeblock-include #'h))
13+
14+
@(for-each (λ (f) (ev `(require (file ,(path->string (build-path notes "knock" f))))))
15+
'("interp.rkt" "compile.rkt" "asm/interp.rkt" "asm/printer.rkt"))
16+
17+
@title[#:tag "Knock"]{Knock: first-class function (pointers)}
18+
19+
@table-of-contents[]
20+
21+
@section[#:tag-prefix "knock"]{First-class function (pointers)}
22+
23+
With Iniquity and Jig, we have introduced functions and function
24+
calls, but functions are second-class language mechanisms: functions
25+
are not values. They cannot be computed. They cannot be stored in a
26+
list or box. They cannot be passed as arguments are returned as
27+
results of other functions.
28+
29+
This is too bad since so many program designs depend on the idea of
30+
computation-as-a-value at the heart of functional and object-oriented
31+
programming.
32+
33+
Let's now remedy this problem by making functions first-class values
34+
in our language. We'll call it @bold{Knock}.
35+
36+
37+
We add to the syntax two new forms, one for reference functions and
38+
one for calling a function where the function position is an arbitrary
39+
expression (that should evaluate to a function):
40+
41+
@verbatim|{
42+
;; type Expr =
43+
;; | ....
44+
;; | `(fun ,Variable)
45+
;; | `(call ,Expr ,@(Listof Expr))
46+
}|
47+
48+
These new syntactic forms are temporary forms that don't correspond
49+
anything in Racket but make it a bit easier to present how first-class
50+
functions work. The @racket[(fun _f)] form is a reference to a
51+
function @racket[_f], which is defined in the program. The
52+
@racket[(call _e0 _es ...)] form is a function call where the function
53+
position, @racket[_e0] is an arbitrary expression that should produce
54+
a function value.
55+
56+
We will end up eliminating them in future versions of the compiler;
57+
they are simply a crutch for now.
58+
59+
@section[#:tag-prefix "knock"]{A Compiler with Function pointers}
60+
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+
137+
@codeblock-include["knock/compile.rkt"]
138+
139+
We can verify that the compiler works for programs that use functions
140+
like before:
141+
142+
@ex[
143+
(asm-interp
144+
(compile '(begin (define (f x)
145+
(if (zero? x)
146+
0
147+
(add1 (call (fun f) (sub1 x)))))
148+
(call (fun f) 10))))
149+
]
150+
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+
161+
@ex[
162+
(asm-interp
163+
(compile '(begin (define (f x) (fun h))
164+
(define (h y) y)
165+
(call (call (fun f) 5) 9))))
166+
]

www/notes/knock/asm/printer.rkt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
[`(neg ,a1)
3232
(string-append "\tneg " (arg->string a1) "\n")]
3333
[`(call ,l)
34-
(string-append "\tcall " (label->string l) "\n")]
34+
(string-append "\tcall " (arg->string l) "\n")]
3535
[`(push ,r)
3636
(string-append "\tpush " (reg->string r) "\n")]
3737
[l (string-append (label->string l) ":\n")]))

www/notes/knock/compile.rkt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,17 @@
1010
(define type-box #b001)
1111
(define type-pair #b010)
1212
(define type-string #b011)
13-
(define type-proc #b100)
13+
(define type-proc #b100) ;; <-- NEW: procedure value
1414

1515
(define imm-shift (+ 2 result-shift))
1616
(define imm-type-mask (sub1 (arithmetic-shift 1 imm-shift)))
1717
(define imm-type-int (arithmetic-shift #b00 result-shift))
1818
(define imm-type-bool (arithmetic-shift #b01 result-shift))
1919
(define imm-type-char (arithmetic-shift #b10 result-shift))
2020
(define imm-type-empty (arithmetic-shift #b11 result-shift))
21-
22-
23-
2421
(define imm-val-false imm-type-bool)
25-
(define imm-val-true (bitwise-ior (arithmetic-shift 1 (add1 imm-shift)) imm-type-bool))
22+
(define imm-val-true
23+
(bitwise-ior (arithmetic-shift 1 (add1 imm-shift)) imm-type-bool))
2624

2725
;; Allocate in 64-bit (8-byte) increments, so pointers
2826
;; end in #b000 and we tag with #b001 for boxes, etc.

0 commit comments

Comments
 (0)