Allocate and pass closures on the heap
authorLuke Lau <luke_lau@icloud.com>
Sat, 27 Jul 2019 22:18:01 +0000 (23:18 +0100)
committerLuke Lau <luke_lau@icloud.com>
Sat, 27 Jul 2019 22:18:01 +0000 (23:18 +0100)
codegen.scm

index 78ebbad84bbbed8cab7cc3c013ad395d53f91eec..dda14713ad69a2eda8f676387f3497ff647f735b 100644 (file)
   (format "_lambda~a" (- cur-lambda 1)))
 
                                        ; a closure on the heap looks like:
-; 0-x    x+0   x+4       x+12      x+20
-; label #vars var1....  var2....  var3....
+                                       ; 0    8         16        24
+                                       ; addr var1....  var2....  var3....
 
 (define (codegen-closure label captured si env)
-  (let* ((heap-offsets (range 4 (length captured))) ; 4, 12, 20, etc.
-        (inner-si (- si (* (length captured) wordsize))))
-    (emit "movl $~a, (heap_start)")
-    (emit "add $4, (heap_start)")
-    (for-each (lambda (var-name new-offset)
-               (emit "movq ~a(%rbp), ~a(heap_start)" ; todo: do we need to copy this?
+  (let* ((heap-offsets (map (lambda (i) (+ 8 (* 8 i)))
+                           (range 0 (length captured))))) ; 4, 12, 20, etc.
+
+    (emit "movq heap_start@GOTPCREL(%rip), %rbx")
+    
+    (emit "movq (%rbx), %rax")          ; %rax = heap addr of closure
+
+
+    ; point heap_start to next space
+    (emit "addq $~a, (%rbx)" (+ 8 (* 8 (length captured))))
+
+                                       ; store the address to the lambda code
+    (emit "movq ~a@GOTPCREL(%rip), %rbx" label)
+    (emit "movq %rbx, 0(%rax)")
+
+                                       ; store the captured vars
+    (for-each
+     (lambda (var-name new-offset)
+       (emit "movq ~a(%rbp), ~a(rax)"
             (cdr (assoc var-name env))
-                     new-offset)
-               (emit "add $8, (heap_start)")
+            new-offset))
      captured
-             stack-offsets)
-)))
+     heap-offsets)))
+
                                        ; for now we can only call closures
-(define (codegen-call closure args si env)
-;  (codegen-expr closure si env)
-  (when (not (eq? (ast-type closure) 'closure))
-    (error #f (format "~a is not a closure" closure)))
-  (let* ((captured (caddr closure))
-        (label (cadr closure))
-        (argument-start (length captured)))
-
-                                       ; first move the captured variables into param registers
-    (for-each
-     (lambda (e i)
-       (emit "movq ~a(%rbp), ~a"
-            (cdr (assoc e env)) ; offset of the var
-            (param-register i)))
-     captured (range 0 (length captured)))
+(define (codegen-call f args si env)
+  (codegen-expr f si env)
+
+  (emit "## starting call")
   
-                                       ; then codegen the arguments and move them into the next param registers
+  (emit "movq %rax, ~a(%rbp)" si) ; store address of closure first on stack
+  
+                                       ; codegen the arguments, store them intermediately
   (for-each
    (lambda (e i)
      (begin
-        (codegen-expr e si env)
-                                       ; move result to correct param register
-        (emit "movq %rax, ~a" (param-register i))))
-     args (range argument-start (length args)))
+       (emit "## arg no. ~a" (- i 1))
+       (codegen-expr e (- si (* wordsize i)) env)
+                                       ; store intermediate result on stack
+       (emit "movq %rax, ~a(%rbp)" (- si (* wordsize i)))))
+
+   args (range 1 (length args)))
+
+                                       ; now that we have everything we need on the stack,
+                                       ; move them into the param registers
+
+  (emit "## moving args into place")
+  (for-each
+   (lambda (i) (emit "movq ~a(%rbp), ~a"
+                    (- si (* wordsize i))
+                    (param-register i)))
+   (range 1 (length args)))
+
+                                       ; todo: can this be made more efficient
+  (emit "movq ~a(%rbp), %rax" si)       ; load back pointer to closure
+
+  (emit "## moving captives into place")
+  
+                                       ; move captives into first argument
+  (emit "movq %rax, %rbx")
+  (emit "addq $8, %rbx")
+  (emit "movq %rbx, ~a" (param-register 0))
+
+  (emit "## performing call")
 
   (emit "addq $~a, %rsp" si) ; adjust the stack pointer to account all the stuff we put in the env
-    (emit "callq ~a" label)
-    (emit "subq $~a, %rsp" si)))
+  (emit "callq *(%rax)")                ; call closure function
+  (emit "subq $~a, %rsp" si))
+
+                                       ; LAMBDAS:
+                                       ; 1st param: pointer to captured args
+                                       ; 2nd param: 1st arg
+                                       ; 3rd param: 2nd arg, etc.
 
 (define (codegen-lambda l)
   (let* ((label (car l))
-        (args (cadr l))
-        (captured (caddr l))
-        (body (cadddr l))
+        (stuff (cdr l))
+        (captured (car stuff))
+        (args (cadr stuff))
+        (body (caddr stuff))
                                        ; params = what actually gets passed
         (params (append captured args))
 
-        (param-registers (map param-register
-                              (range 0 (length params))))
         (stack-offsets (map (lambda (i)
                               (* (- wordsize) i))
                             (range 1 (length params))))
 
-        (copy-insts (map (lambda (r o)
-                           (format "movq ~a, ~a(%rbp)" r o))
-                         param-registers stack-offsets))
-
         (env (map cons params stack-offsets)))
     (emit "~a:" label)
+
+    (display "## lambda captives: ")
+    (display captured)
+    (newline)
+    (display "## lambda args: ")
+    (display args)
+    (newline)
     (display "## lambda body: ")
     (display body)
     (newline)
-    (display "## environment: ")
-    (display env)
-    (newline)
     
     (emit "push %rbp") ; preserve caller's base pointer
     (emit "movq %rsp, %rbp") ; set up our own base pointer
 
-    (for-each emit copy-insts)
+                                       ; load the captured vars onto the stack
+    (for-each
+     (lambda (i)
+       (emit "movq ~a(~a), ~a(%rbp)"
+            i (param-register 0) (* (- wordsize) i)))
+       (range 0 (length captured)))
+
+                                       ; load the args onto the stack
+    (for-each
+     (lambda (i)
+       (emit "movq ~a, ~a(%rbp)"
+            (param-register i) (* (- wordsize) i)))
+     (range 1 (length args)))
+          
     (codegen-expr body (* (- wordsize) (+ 1 (length params))) env)
 
     (emit "pop %rbp") ; restore caller's base pointer
 
 (define (codegen-expr e si env)
   (case (ast-type e)
-    ('builtin e)
     ('closure (codegen-closure (cadr e) (caddr e) si env))
     ('app
-     (let ((callee (codegen-expr (car e) si env)))
-       (case callee
+     (case (car e)
        ('+ (codegen-add (cdr e) si env))
        ('- (codegen-sub (cadr e) (caddr e) si env))
        ('* (codegen-mul (cadr e) (caddr e) si env))
        ('= (codegen-eq  (cadr e) (caddr e) si env))
        ('bool->int (codegen-expr (cadr e) si env))
        ('print (codegen-print (cadr e) si env))
-        (else (codegen-call callee (cdr e) si env)))))
+       (else (codegen-call (car e) (cdr e) si env))))
+
+    ; this is a builtin being passed around as a variable
+    ('builtin (emit "movq $~a, %rax" (builtin-id e)))
 
     ('let (codegen-let (let-bindings e)
                       (let-body e)
       (else '())))
   (collect prog))
 
-                                       ; ((lambda (x) (+ x 1)) 42) => {lambda0: (x) (+ x 1)}, (@lambda0 42)
+                                       ; ((lambda (x) (+ x y)) 42) => ((closure lambda1 (y)) 42)
+                                        ;                              [(lambda1 . ((y), (x), (+ x y))]
+                                       ; for builtins, this generates a closure if it is used
+                                       ; outside of an immediate app
+                                       ; but only one closure for each builtin
+
 (define (extract-lambdas program)
   (define lambdas '())
   (define (add-lambda e)
           (args (lambda-args e))
           (captured (free-vars e))
           (body (extract (lambda-body e)))
-          (new-lambda (list label args captured body)))
+          (new-lambda (cons label (list captured args body))))
       (set! lambdas (cons new-lambda lambdas))
       `(closure ,label ,captured))) ; todo: should we string->symbol?
+
+  (define (find-builtin-lambda e)
+    (let [(l (assq (builtin-name e) lambdas))]
+      (if l `(closure ,(car l) ,(caadr l)) #f)))
+
+  (define (builtin-name e)
+    (case e
+      ('+ "_add")
+      ('- "_sub")
+      ('* "_mul")
+      ('bool->int "_bool2int")
+      (else (error #f "fill this out"))))
+  (define (builtin-args e)
+    (case e
+      ('+ '(x y))
+      ('- '(x y))
+      ('* '(x y))
+      ('bool->int '(x))
+      (else (error #f "fill this out"))))
+
+  (define (add-builtin-lambda e)
+    (let* [(label (builtin-name e))
+          (captured '())
+          (args (builtin-args e))
+          (body `(,e ,@args))
+          (new-lambda (cons label (list captured args body)))]
+      (set! lambdas (cons new-lambda lambdas))
+      `(closure ,label ,captured)))
+  
   (define (extract e)
     (case (ast-type e)
       ('lambda (add-lambda e))
       ('let `(let ,(map extract (let-bindings e))
               ,@(map extract (let-body e))))
-      ('app (append (list (extract (car e)))
+      ('app (append
+                                       ; if a builtin is used as a function, don't generate lambda
+            (if (eqv? 'builtin (ast-type (car e)))
+                '()
+                (list (extract (car e))))
             (map extract (cdr e))))
+      
+      ('builtin
+       (if (find-builtin-lambda e)
+          (find-builtin-lambda e)
+          (add-builtin-lambda e)))
+
+       
       (else (ast-traverse extract e))))
   (let ((transformed (extract program)))
     (cons lambdas transformed)))
     (else (error #f "need to test out the below"))
     (else (format "~a(%rsp)" (- n 6)))))
 
+(define (initialize-heap)
+  (let ((mmap
+        (case target
+          ('darwin "0x20000c5")
+          ('linux "9"))))
+                                       ; allocate some heap memory
+    (emit "mov $~a, %rax" mmap) ; mmap
+    (emit "xor %rdi, %rdi")  ; addr = null
+    (emit "movq $1024, %rsi")   ; length = 1kb
+    (emit "movq $0x3, %rdx") ; prot = read | write = 0x2 | 0x1
+                                       ;    flags = anonymous | private
+    (case target
+      ('darwin (emit "movq $0x1002, %r10")) ; anon = 0x1000, priv = 0x02
+      ('linux (emit "movq $0x22, %r10")))   ; anon = 0x20,   priv = 0x02
+    (emit "movq $-1, %r8") ; fd = -1
+    (emit "xor %r9, %r9") ; offset = 0
+    (emit "syscall")
+                                       ; %rax now contains pointer to the start of the heap
+                                       ; keep track of it
+
+    (emit "movq heap_start@GOTPCREL(%rip), %rsi")
+    (emit "movq %rax, (%rsi)")))
+
 (define (codegen program)
   (let* ((extract-res-0 (extract-strings program))
         (strings (car extract-res-0))
 
     (emit "_start:")
 
-    ; allocate some heap memory
-    (emit "mov $9, %rax") ; mmap
-    (emit "xor %rdi, %rdi")  ; addr = null
-    (emit "movq $1024, %rsi")   ; length = 1kb
-    (emit "movq $0x3, %rdx") ; prot = read | write = 0x2 | 0x1
-    (emit "movq $0x22, %r10") ; flags = anonymous | private = 0x20 | 0x02
-    (emit "movq $-1, %r8") ; fd = -1
-    (emit "xor %r9, %r9") ; offset = 0
-    (emit "syscall")
-
-    ; %rax now contains pointer to the start of the heap
-    ; keep track of it
-    (emit "movq %rax, (heap_start)")
+    (initialize-heap)
 
     (emit "movq %rsp, %rbp")            ; set up the base pointer
     (codegen-expr xform-prog 0 '())
        (system (format "ld /tmp/a.o -o ~a" output))))))
 
 ; NOTES
-; syscalls in linux use the following arguments for syscall instruction:
+; syscalls in linux and darwin use the following arguments for syscall instruction:
 ; %rax = syscall #
 ; %rdi = 1st arg
 ; %rsi = 2nd arg
 ; %r10 = 4th arg
 ; %r8  = 5th arg
 ; %r9  = 6th arg
+
+; on darwin, the syscall is offset by 0x2000000
+; https://opensource.apple.com/source/xnu/xnu-2782.20.48/bsd/kern/syscalls.master
+; documentation for most syscalls: /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sys