~ chicken-core (chicken-5) 2a94e86f0aac76e42b31135ba127306464219794
commit 2a94e86f0aac76e42b31135ba127306464219794 Author: felix <felix@call-with-current-continuation.org> AuthorDate: Wed Dec 8 22:14:22 2010 +0100 Commit: felix <felix@call-with-current-continuation.org> CommitDate: Wed Dec 8 22:14:22 2010 +0100 added lambda-lifter back again as it proved to be effective in one use case diff --git a/batch-driver.scm b/batch-driver.scm index 0d6372c8..dff1c857 100644 --- a/batch-driver.scm +++ b/batch-driver.scm @@ -182,6 +182,7 @@ (not a-only)) (set! all-import-libraries #t)) (set! enable-module-registration (not (memq 'no-module-registration options))) + (when (memq 'lambda-lift options) (set! do-lambda-lifting #t)) (when (memq 'scrutinize options) (set! do-scrutinize #t)) (when (memq 't debugging-chicken) (##sys#start-timer)) (when (memq 'b debugging-chicken) (set! time-breakdown #t)) @@ -508,6 +509,19 @@ (end-time "scrutiny") (set! first-analysis #t) ) + (when do-lambda-lifting + (begin-time) + (unless do-scrutinize ; no need to do analysis if already done above + (set! first-analysis #f) + (set! db (analyze 'lift node0)) + (print-db "analysis" '|0| db 0) + (end-time "pre-analysis (lambda-lift)")) + (begin-time) + (perform-lambda-lifting! node0 db) + (end-time "lambda lifting") + (print-node "lambda lifted" '|L| node0) + (set! first-analysis #t) ) + (let ((req (concatenate (vector->list file-requirements)))) (when (debugging 'M "; requirements:") (pp req)) diff --git a/c-platform.scm b/c-platform.scm index ae4a52a4..e95b68b4 100644 --- a/c-platform.scm +++ b/c-platform.scm @@ -84,8 +84,7 @@ check-syntax to-stdout no-usual-integrations case-insensitive no-lambda-info profile inline keep-shadowed-macros ignore-repository fixnum-arithmetic disable-interrupts optimize-leaf-routines - lambda-lift ; OBSOLETE - compile-syntax tag-pointers accumulate-profile + lambda-lift compile-syntax tag-pointers accumulate-profile disable-stack-overflow-checks raw emit-external-prototypes-first release local inline-global analyze-only dynamic scrutinize no-argc-checks no-procedure-checks diff --git a/chicken.scm b/chicken.scm index cd840e07..441bd072 100644 --- a/chicken.scm +++ b/chicken.scm @@ -100,7 +100,7 @@ (when (>= level 5) (set! options (cons* 'disable-interrupts 'no-trace 'unsafe 'block - 'optimize-leaf-routines 'no-lambda-info + 'optimize-leaf-routines 'lambda-lift 'no-lambda-info 'inline 'inline-global 'unboxing options) ) ) ) ) (loop (cdr rest)) ) ) diff --git a/compiler-namespace.scm b/compiler-namespace.scm index 28ce46aa..91c9b47e 100644 --- a/compiler-namespace.scm +++ b/compiler-namespace.scm @@ -98,6 +98,7 @@ display-analysis-database display-line-number-database display-real-name-table + do-lambda-lifting do-scrutinize dump-defined-globals dump-global-refs @@ -219,6 +220,7 @@ perform-cps-conversion perform-high-level-optimizations perform-inlining! + perform-lambda-lifting! perform-pre-optimization! perform-unboxing! posq diff --git a/compiler.scm b/compiler.scm index 2cc164a5..0c526fa9 100644 --- a/compiler.scm +++ b/compiler.scm @@ -57,6 +57,7 @@ ; (hide {<name>}) ; (inline-limit <limit>) ; (keep-shadowed-macros) +; (lambda-lift) ; (no-argc-checks) ; (no-bound-checks) ; (no-procedure-checks) @@ -320,6 +321,7 @@ (define disable-stack-overflow-checking #f) (define require-imports-flag #f) (define external-protos-first #f) +(define do-lambda-lifting #f) (define inline-max-size default-inline-max-size) (define emit-closure-info #t) (define undefine-shadowed-macros #t) @@ -1400,7 +1402,7 @@ (for-each export-variable syms))) ((emit-external-prototypes-first) (set! external-protos-first #t) ) - ((lambda-lift) #f) ; OBSOLETE + ((lambda-lift) (set! do-lambda-lifting #t)) ((inline) (if (null? (cdr spec)) (set! inline-locally #t) diff --git a/csc.scm b/csc.scm index 4067ea07..65d35695 100644 --- a/csc.scm +++ b/csc.scm @@ -133,8 +133,7 @@ '(-explicit-use -no-trace -no-warnings -no-usual-integrations -optimize-leaf-routines -unsafe -block -disable-interrupts -fixnum-arithmetic -to-stdout -profile -raw -accumulate-profile -check-syntax -case-insensitive -shared -compile-syntax -no-lambda-info - -lambda-lift ; OBSOLETE - -dynamic -disable-stack-overflow-checks -local + -lambda-lift -dynamic -disable-stack-overflow-checks -local -emit-external-prototypes-first -inline -release -scrutinize -analyze-only -keep-shadowed-macros -inline-global -ignore-repository -no-symbol-escape -no-parentheses-synonyms -r5rs-syntax @@ -372,6 +371,7 @@ Usage: #{csc} FILENAME | OPTION ... -b -block enable block-compilation -disable-interrupts disable interrupts in compiled code -f -fixnum-arithmetic assume all numbers are fixnums + -lambda-lift perform lambda-lifting -disable-stack-overflow-checks disables detection of stack-overflows -inline enable inlining -inline-limit LIMIT set inlining threshold diff --git a/manual/Declarations b/manual/Declarations index 56b1a458..99a1e225 100644 --- a/manual/Declarations +++ b/manual/Declarations @@ -161,6 +161,13 @@ Normally, when a toplevel variable is assigned or defined that has the same name will be removed (in addition to showing a warning). This declaration will disable the removal of the macro. +=== lambda-lift + + [declaration specifier] (lambda-lift) + +Enables lambda-lifting (equivalent to the {{-lambda-lift}} option). + + === local [declaration specifier] (local) diff --git a/manual/Using the compiler b/manual/Using the compiler index ebbff6f5..b1c22629 100644 --- a/manual/Using the compiler +++ b/manual/Using the compiler @@ -41,6 +41,7 @@ the source text should be read from standard input. s show program-size information and other statistics a show node-matching during simplification p show execution of compiler sub-passes + l show lambda-lifting information m show GC statistics during compilation n print the line-number database c print every expression before macro-expansion @@ -49,8 +50,10 @@ the source text should be read from standard input. x display information about experimental features D when printing nodes, use node-tree output N show the real-name mapping table + 0 show database before lambda-lifting pass S show applications of compiler syntax T show expressions after converting to node tree + L show expressions after lambda-lifting U show expressions after unboxing M show syntax-/runtime-requirements 1 show source expressions @@ -117,6 +120,8 @@ the source text should be read from standard input. ; -keep-shadowed-macros : Do not remove macro definitions with the same name as assigned toplevel variables (the default is to remove the macro definition). +; -lambda-lift : Enable the optimization known as lambda-lifting. + ; -local : Assume toplevel variables defined in the current compilation unit are not externally modified. This gives the compiler more opportunities for inlining. Note that this may result in counter-intuitive and non-standard behaviour: an asssignment to an exported toplevel variable executed in a different compilation unit or in evaluated code will possibly not be seen by code executing in the current compilation unit. ; -module : wraps the compiled code in an implicit module named {{main}}, importing the {{scheme}} and {{chicken}} modules. @@ -157,7 +162,7 @@ the source text should be read from standard input. -optimize-level 2 is equivalent to -optimize-leaf-routines -inline -unboxing -optimize-level 3 is equivalent to -optimize-leaf-routines -local -inline -inline-global -unboxing -optimize-level 4 is equivalent to -optimize-leaf-routines -local -inline -inline-global -unboxing -unsafe - -optimize-level 5 is equivalent to -optimize-leaf-routines -block -inline -inline-global -unsafe -unboxing -disable-interrupts -no-trace -no-lambda-info + -optimize-level 5 is equivalent to -optimize-leaf-routines -block -inline -inline-global -unsafe -unboxing -lambda-lift -disable-interrupts -no-trace -no-lambda-info ; -output-file FILENAME : Specifies the pathname of the generated C file. Default is {{FILENAME.c}}. diff --git a/optimizer.scm b/optimizer.scm index 839b6caf..0cdf29d9 100644 --- a/optimizer.scm +++ b/optimizer.scm @@ -33,6 +33,8 @@ (include "compiler-namespace") (include "tweaks") +(define-constant maximal-number-of-free-variables-for-liftable 16) + ;;; Scan toplevel expressions for assignments: @@ -1473,3 +1475,367 @@ (debugging 'p "direct leaf routine optimization pass...") (walk #f node #f) dirty) ) + + +;;; Lambda lift: +; +; - Find lambda-liftable local procedures and lift them to toplevel. +; - Pass free variables as extra parameters, including the free variables of +; other lifted procedures. This implies that lifted procedures that call each +; other have to be in the same scope. +; - Declare the lifted procedures (effectively) as bound-to-procedure and block-global. + +(define (perform-lambda-lifting! node db) + (let ([lambda-values '()] + [eliminated '()] ) + + (define (find-lifting-candidates) + ;; Collect potentially liftable procedures and return as a list of (<name> . <value>) pairs: + ;; - Also build a-list that maps lambda-nodes to names. + (let ((cs '())) + (##sys#hash-table-for-each + (lambda (sym plist) + (and-let* ((val (assq 'value plist)) + (refs (assq 'references plist)) + (css (assq 'call-sites plist)) ) + (let ((nrefs (length (cdr refs)))) + (when (and (not (assq 'unknown plist)) + (eq? 'lambda (node-class (cdr val))) + (not (assq 'global plist)) + #;(> nrefs 1) + (= nrefs (length (cdr css))) ) + (set! lambda-values (alist-cons (cdr val) sym lambda-values)) + (set! cs (alist-cons sym (cdr val) cs)) ) ) ) ) + db) + cs) ) + + (define (build-call-graph cs) + ;; Build call-graph of the form ((<name> (<free1> ...) <called1> ...) ...): + (let ([g '()] + [free '()] + [called '()] ) + + (define (walk n env) + (let ([class (node-class n)] + [params (node-parameters n)] + [subs (node-subexpressions n)] ) + (case class + [(##core#variable set!) + (let ([var (first params)]) + (unless (or (memq var env) (get db var 'global)) + (set! free (cons var free)) ) + (when (assq var cs) (set! called (cons var called))) + (for-each (lambda (n) (walk n env)) subs) ) ] + [(let) + (let loop ([vars params] [vals subs]) + (if (null? vars) + (walk (car vals) (append params env)) + (let ([var (car vars)]) + (walk (car vals) env) + (loop (cdr vars) (cdr vals)) ) ) ) ] + [(lambda) + (decompose-lambda-list + (first params) + (lambda (vars argc rest) (walk (first subs) (append vars env))) ) ] + [else (for-each (lambda (n) (walk n env)) subs)] ) ) ) + + (for-each + (lambda (cs) + (let* ([here (car cs)] + [lval (cdr cs)] + [llist (car (node-parameters lval))] ) + (set! free '()) + (set! called '()) + (decompose-lambda-list + llist + (lambda (vars arg rest) + (walk (car (node-subexpressions lval)) vars) ) ) + (set! g (alist-cons here (cons free called) g)) ) ) + cs) + g) ) + + (define (eliminate cs graph) + ;; Eliminate all liftables that have free variables that are assigned to (and are not liftable), + ;; or that have more than N free variables (including free variables of called liftables): + (remove + (lambda (gn) + (or (> (count-free-variables (car gn) graph) maximal-number-of-free-variables-for-liftable) + (any (lambda (v) + (and (get db v 'assigned) + (not (assq v cs)) ) ) + (second gn) ) ) ) + graph) ) + + (define (count-free-variables name graph) + (let ([gnames (unzip1 graph)]) + (let count ([n name] [walked '()]) + (let* ([a (assq n graph)] + [cs (lset-difference eq? (cddr a) walked gnames)] + [f (length (delete-duplicates (second a) eq?))] + [w2 (cons n (append cs walked))] ) + (fold + f (map (lambda (c) (count c w2)) cs)) ) ) ) ) + + (define (collect-accessibles graph) + ;; Collect accessible variables for each liftable into list of the form (<name> <accessible1> ...): + (let ([al '()]) + (let walk ([n node] [vars '()]) + (let ([class (node-class n)] + [params (node-parameters n)] + [subs (node-subexpressions n)] ) + (case class + [(##core#variable quote ##core#undefined ##core#primitive ##core#proc) #f] + [(let) + (let loop ([vars2 params] [vals subs]) + (if (null? vars2) + (walk (car vals) (append params vars)) + (begin + (walk (car vals) vars) + (loop (cdr vars2) (cdr vals)) ) ) ) ] + [(lambda) + (let ([lval (assq n lambda-values)]) + (when lval + (let ([name (cdr lval)]) + (when (assq name graph) + (set! al (alist-cons (cdr lval) vars al))) ) ) ) + (decompose-lambda-list + (first params) + (lambda (vars2 argc rest) + (walk (car subs) (append vars2 vars)) ) ) ] + [else + (for-each (lambda (n) (walk n vars)) subs) ] ) ) ) + al) ) + + (define (eliminate2 graph al) + ;; Eliminate liftables that have call-sites without access to all free variables; + (remove + (lambda (gn) + (let* ([name (first gn)] + [free (second gn)] ) + (any (lambda (gn2) + (and (memq name (cddr gn2)) ; callee? + (lset<= eq? (cdr (assq (car gn2) al)) free) ) ) + graph) ) ) + graph) ) + + (define (eliminate3 graph) + ;; Eliminate liftables that call other eliminated liftables: + ;; - repeat until nothing changes. + (let loop ([graph graph] [n (length graph)]) + (let* ([g2 (filter (lambda (gn) (every (lambda (n) (assq n graph)) (cddr gn))) graph)] + [n2 (length g2)] ) + (if (= n n2) + g2 + (loop g2 n2) ) ) ) ) + + (define (eliminate4 graph) + ;; Eliminate liftables that have unknown call-sites which do not have access to + ;; any of the free variables of all callees: + (let walk ([n node] [vars '()]) + (let ([class (node-class n)] + [params (node-parameters n)] + [subs (node-subexpressions n)] ) + (case class + [(##core#variable quote ##core#undefined ##core#primitive ##core#proc) #f] + [(let) + (let loop ([vars2 params] [vals subs]) + (if (null? vars2) + (walk (car vals) (append params vars)) + (begin + (walk (car vals) vars) + (loop (cdr vars2) (cdr vals)) ) ) ) ] + [(lambda) + (decompose-lambda-list + (first params) + (lambda (vars2 argc rest) + (walk (car subs) (append vars2 vars)) ) ) ] + [(##core#call) + (let ([fn (first subs)]) + (call-with-current-continuation + (lambda (return) + (when (eq? '##core#variable (node-class fn)) + (let ([done '()]) + (let loop ([name (first (node-parameters fn))]) + (unless (memq name done) + (set! done (cons name done)) + (let ([gn (assq name graph)]) + (when gn + (unless (lset<= eq? (second gn) vars) + #;(print "*** " (first (node-parameters fn)) " | " name ": " vars " / " (second gn)) + (set! graph (delete! gn graph eq?)) + (return #f) ) + (for-each loop (cddr gn)) ) ) ) ) ) ) ) ) + (for-each (lambda (n) (walk n vars)) subs) ) ] + [else (for-each (lambda (n) (walk n vars)) subs)] ) ) ) + graph) + + (define (compute-extra-variables graph) + ;; Gather variables that have to be passed additionally: + ;; - do not pass variables that are defined inside the body of a liftable. + (define (defined n) + (let ([defd '()]) + (let walk ([n n]) + (let ([class (node-class n)] + [params (node-parameters n)] + [subs (node-subexpressions n)] ) + (case class + [(let) + (set! defd (append params defd)) + (for-each walk subs) ] + [(lambda) + (decompose-lambda-list + (first params) + (lambda (vars argc rest) + (set! defd (append vars defd)) + (walk (first subs)) ) ) ] + [else (for-each walk subs)] ) ) ) + defd) ) + (let ([extras (map (lambda (gn) (cons (first gn) (second gn))) graph)] + [walked '()] ) + (define (walk gn) + (let ([name (car gn)]) + ;; Hm. To handle liftables that are called recursively (but indirect) I use this kludge. Is it safe? + (unless (> (count (cut eq? name <>) walked) 1) + (set! walked (cons name walked)) + (let ([callees (cddr gn)]) + (for-each (lambda (c) (walk (assq c graph))) callees) + (let ([f (assq name extras)]) + (set-cdr! f (append (cdr f) (concatenate (map (lambda (n2) (cdr (assq n2 extras))) callees)))) ) ) ) ) ) + (for-each walk graph) + (map (lambda (xt) + (let* ([name (car xt)] + [defd (defined (get db name 'value))] ) + (cons name + (remove + (lambda (v) + (or (assq v graph) + (memq v defd) ) ) + (delete-duplicates (cdr xt) eq?)) ) ) ) + extras) ) ) + + (define (reconstruct! graph extra) + ;; Reconstruct node tree by adding global definitions: + (node-subexpressions-set! + node + (list + (fold-right + (lambda (gn body) + (let* ([name (car gn)] + [lval (get db name 'value)] ) + (hide-variable name) + (decompose-lambda-list + (first (node-parameters lval)) + (lambda (vars argc rest) + (let* ([xvars (cdr (assq name extra))] + [xaliases (map gensym xvars)] + [xmap (map cons xvars xaliases)] ) + (rename-extra-variables! (first (node-subexpressions lval)) xmap) + (make-node + 'let (list (gensym 't)) + (list (make-node + 'set! (list name) + (list + (make-node + 'lambda + (list (build-lambda-list (append xaliases vars) (+ argc (length xvars)) rest)) + (node-subexpressions lval) ) ) ) + body) ) ) ) ) ) ) + (first (node-subexpressions node)) + graph) ) ) ) + + (define (rename-extra-variables! node xmap) + ;; Rename variables from a given map: + (define (rename v) + (let ([a (assq v xmap)]) + (if a (cdr a) v) ) ) + (let walk ([n node]) + (let ([class (node-class n)] + [params (node-parameters n)] + [subs (node-subexpressions n)] ) + (case class + [(let) + (node-parameters-set! n (map rename params)) + (for-each walk subs) ] + [(##core#variable) + (node-parameters-set! n (list (rename (first params)))) ] + [(set!) + (node-parameters-set! n (list (rename (first params)))) + (for-each walk subs) ] + [(lambda) + (decompose-lambda-list + (first params) + (lambda (vars argc rest) + (set-car! params (build-lambda-list (map rename vars) argc rest)) + (walk (first subs)) ) ) ] + [else (for-each walk subs)] ) ) ) ) + + (define (extend-call-sites! extra) + ;; Change call sites by adding extra variables: + (let walk ([n node]) + (let ([class (node-class n)] + [params (node-parameters n)] + [subs (node-subexpressions n)] ) + (case class + [(##core#call) + (let ([fn (first subs)]) + (when (eq? '##core#variable (node-class fn)) + (let ([a (assq (first (node-parameters fn)) extra)]) + (when a + (set-car! params #t) + (node-subexpressions-set! + n + (cons fn (append (map varnode (cdr a)) (cdr subs))) ) ) ) ) + (for-each walk (node-subexpressions n)) ) ] + [else (for-each walk subs)] ) ) ) ) + + (define (remove-local-bindings! graph) + ;; Remove local definitions of lifted procedures: + (let walk ([n node]) + (let ([class (node-class n)] + [params (node-parameters n)] + [subs (node-subexpressions n)] ) + (case class + [(let) + (for-each walk (node-subexpressions n)) + (let ([vars2 '()] + [vals2 '()] ) + (do ([vars params (cdr vars)] + [vals subs (cdr vals)] ) + ((null? vars) + (cond [(null? vars2) (copy-node! (car vals) n)] + [else + (node-parameters-set! n (reverse vars2)) + (node-subexpressions-set! n (append (reverse vals2) vals)) ] ) ) + (unless (assq (car vars) graph) + (set! vars2 (cons (car vars) vars2)) + (set! vals2 (cons (car vals) vals2)) ) ) ) ] + [(set!) + (for-each walk (node-subexpressions n)) + (when (assq (first params) graph) + (node-class-set! n '##core#undefined) + (node-parameters-set! n '()) + (node-subexpressions-set! n '()) ) ] + [else (for-each walk subs)] ) ) ) ) + + (debugging 'p "gathering liftables...") + (let ([cs (find-lifting-candidates)]) + (debugging 'p "building call graph...") + (let ([g (build-call-graph cs)]) + (debugging 'p "eliminating non-liftables...") + (let ([g2 (eliminate cs g)]) + (when (debugging 'l "call-graph:") (pretty-print g2)) + (debugging 'p "computing access-lists...") + (let ([al (collect-accessibles g2)]) + (when (debugging 'l "accessibles:") (pretty-print al)) + (debugging 'p "eliminating liftables by access-lists and non-liftable callees...") + (let ([ls (eliminate3 (eliminate4 g2))]) ;(eliminate2 g2 al)))]) - why isn't this used? + (when (pair? ls) + (debugging 'o "liftable local procedures" (delay (unzip1 ls)))) + (debugging 'p "gathering extra parameters...") + (let ([extra (compute-extra-variables ls)]) + (when (debugging 'l "additional parameters:") (pretty-print extra)) + (debugging 'p "changing call sites...") + (extend-call-sites! extra) + (debugging 'p "removing local bindings...") + (remove-local-bindings! ls) + (debugging 'p "moving liftables to toplevel...") + (reconstruct! ls extra) ) ) ) ) ) ) ) ) diff --git a/support.scm b/support.scm index da244319..f8b784c9 100644 --- a/support.scm +++ b/support.scm @@ -1282,6 +1282,7 @@ Usage: chicken FILENAME OPTION ... -optimize-level NUMBER enable certain sets of optimization options -optimize-leaf-routines enable leaf routine optimization + -lambda-lift enable lambda-lifting -no-usual-integrations standard procedures may be redefined -unsafe disable all safety checks -local assume globals are only modified in current @@ -1290,7 +1291,7 @@ Usage: chicken FILENAME OPTION ... -disable-interrupts disable interrupts in compiled code -fixnum-arithmetic assume all numbers are fixnums -benchmark-mode equivalent to 'block -optimize-level 4 - -debug-level 0 -fixnum-arithmetic + -debug-level 0 -fixnum-arithmetic -lambda-lift -inline -disable-interrupts' -disable-stack-overflow-checks disables detection of stack-overflows -inline enable inliningTrap