;;;; Cyclone Scheme ;;;; https://github.com/justinethier/cyclone ;;;; ;;;; Copyright (c) 2014-2019, Justin Ethier ;;;; All rights reserved. ;;;; ;;;; This file is part of the cps-optimizations module. ;;;; (cond-expand (program (import (scheme base) (scheme write) (scheme cyclone ast) (scheme cyclone primitives) (scheme cyclone transforms) (scheme cyclone cps-optimizations) (scheme cyclone util) (scheme cyclone pretty-print) (srfi 2) (srfi 69) ) )) ;; symbol -> hash-table -> boolean ;; Is it OK to inline code replacing ref, based on call graph data from lookup table? (define (inline-ok-from-call-graph? ref tbl) ;(let ((result (let ((vars (hash-table-ref/default tbl ref #f))) (call/cc (lambda (return) (when (pair? vars) ;; If there are vars, make sure each one is safe to inline (for-each (lambda (v) (and-let* ((adb-var (adb:get/default v #f))) (when (not (adbv:inlinable adb-var)) ;(trace:error `(cannot inline ,ref)) (return #f)) ) ) (cdr vars))) ;; Skip ref itself (return #t))))) ;) ;(trace:error `(inline-ok-from-call-graph? ,ref ,result ,(hash-table-ref/default tbl ref #f))) ;result)) ;; Analyze call graph. The goal is to be able to figure out which variables a primitive call ;; is dependent upon. We then need to be able to query if any of those variables are mutated ;; (ideally in fnc body) in which case we cannot inline the prim call. (define (analyze:build-call-graph sexp) ;; Add new entry for each var as it is found... (define lookup-tbl (make-hash-table)) ;; Pass over the sexp ;; exp - S-expression to scan ;; vars - alist of current set of variables (define (scan exp vars) ;(trace:error `(DEBUG scan ,(ast:ast->pp-sexp exp))) (cond ((ast:lambda? exp) (for-each (lambda (a) (scan a vars)) (ast:lambda-formals->list exp)) (for-each (lambda (e) (scan e vars)) (ast:lambda-body exp)) ) ((quote? exp) #f) ((const? exp) #f) ((ref? exp) (hash-table-set! lookup-tbl exp vars) ) ((define? exp) (scan (define->exp exp) '())) ((set!? exp) ;; TODO: probably need to keep track of var here (scan (set!->var exp) vars) (scan (set!->exp exp) vars)) ((if? exp) (scan (if->condition exp) vars) (scan (if->then exp) vars) (scan (if->else exp) vars)) ((app? exp) (cond ((ast:lambda? (car exp)) ;; Track deps on lambda var(s) (for-each (lambda (e) (scan e vars)) (ast:lambda-formals->list (car exp))) ;; Scan body, with reset vars (??) (for-each (lambda (e) (scan e '())) (ast:lambda-body (car exp))) ;; Scan lambda arg(s), again also with reset vars (for-each (lambda (e) (scan e '())) (cdr exp)) ) ((and (ref? (car exp)) (list? exp) (> (length exp) 1)) (let* ((cont (cadr exp)) ;; TODO: what if arg is not a ref? Is that possible after cps (probably, with inlining)? (args (filter ref? (cddr exp))) (vars* (append args vars)) ) (scan cont vars*) ;(for-each ; (lambda (e) ; (scan e vars*)) ; (cdr exp)) )) (else (for-each (lambda (e) (scan e vars)) exp)))) (else (error "unknown expression type: " exp)) )) (scan sexp '()) lookup-tbl) (cond-expand (program (define (trace:error exp) (write exp) (newline)) (define sexp '( ;; Test case from compiler benchmark. ;; The original code is: ;; (define (test obj) ;; (let ((m (length (queue->list objects-dumped)))) ;; (queue-put! objects-dumped obj) ;; (queue-put! object-queue obj) ;; m)) ; (define test ; (lambda ; (k$38 obj$5$11) ; (queue->list ; (lambda ; (r$42) ; ((lambda ; (r$39) ; ((lambda ; (m$6$12) ; (queue-put! ; (lambda ; (r$40) ; (queue-put! ; (lambda (r$41) (k$38 m$6$12)) ; object-queue ; obj$5$11)) ; objects-dumped ; obj$5$11)) ; r$39)) ; (length r$42))) ; objects-dumped))) (define test (lambda (k$99 obj$27$45) ((lambda (k$107) (proc-obj? (lambda (r$108) (if r$108 (proc-obj-code (lambda (r$109) (k$107 (not__inline__ r$109))) obj$27$45) (k$107 #f))) obj$27$45)) (lambda (r$100) (if r$100 (k$99 #f) (queue->list (lambda (r$106) (pos-in-list (lambda (r$101) ((lambda (n$29$46) (if n$29$46 (k$99 n$29$46) (queue->list (lambda (r$105) ((lambda (r$102) ((lambda (m$30$47) (queue-put! (lambda (r$103) (queue-put! (lambda (r$104) (k$99 m$30$47)) object-queue obj$27$45)) objects-dumped obj$27$45)) r$102)) (length r$105))) objects-dumped))) r$101)) obj$27$45 r$106)) objects-dumped)))))) ;; Doesn't really matter, but lets leave this for now (define slot-set! (lambda (k$7170 name$2424$3603 obj$2425$3604 idx$2426$3605 val$2427$3606) ((lambda (vec$2428$3607) ((lambda (r$7171) (k$7170 (vector-set! r$7171 idx$2426$3605 val$2427$3606))) (vector-ref vec$2428$3607 2))) obj$2425$3604))) ) ) (pretty-print (ast:ast->pp-sexp (ast:sexp->ast sexp))) (newline) (newline) (let ((ht (analyze:build-call-graph (ast:sexp->ast sexp)))) (pretty-print (hash-table->alist ht)) (newline) ;; TODO: store table and call these to test various vars: (analyze:find-inlinable-vars (ast:sexp->ast sexp) '()) ;; Identify variables safe to inline (pretty-print (inline-ok-from-call-graph? 'm$30$47 ht)) (newline) (pretty-print (inline-ok-from-call-graph? 'zzz ht)) (newline) ) ;(pretty-print ; (ast:ast->pp-sexp ; (opt:local-var-reduction (ast:sexp->ast sexp))) ;) ))