schemify: improve cross-module optimization

The schemify pass collects known-value information as the first step
of processing a linklet body, but the main pass to process the linklet
body may simplify it in a way that exposes new information. For
example, in

 (define (call) (values 1 2))
 (define-values (x y) (call))

the main pass will inline `call` and expose the fact that `x` and `y`
are always 1 and 2, respectively.

Adjust schemify to inspect the simplified form of a definition and
potentially add new information to known-value information, which is
useful later in the ame linklet body and also as cross-module
information.
This commit is contained in:
Matthew Flatt 2019-07-01 11:19:09 -06:00
parent ecabcd385a
commit b0f77a98a3
6 changed files with 144 additions and 57 deletions

View File

@ -2944,6 +2944,32 @@ case of module-leve bindings; it doesn't cover local bindings.
(err/rt-test/once (dynamic-require ''uses-a-in-define-values-before-a-is-defined #f))
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Make sure that inlining doesn't duplicated a quoted list
(let ([e (compile
'(module check-with-inlining-duplicates-by-using-submodules racket/base
(provide check)
(module m racket/base
(provide f)
(define (f x) '(1 2)))
(module n racket/base
(require (submod ".." m))
(provide v)
(define v (f 0)))
(require 'm 'n)
(define check (eq? (f 0) v))))])
(define-values (i o) (make-pipe))
(write e o)
(close-output-port o)
(eval (parameterize ([read-accept-compiled #t])
(read i)))
(test #t dynamic-require ''check-with-inlining-duplicates-by-using-submodules 'check))
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(report-errs)

View File

@ -20,7 +20,7 @@
(optimize orig-rhs prim-knowns primitives knowns imports mutated)
orig-rhs))
(values
(let ([k (infer-known rhs v #t id knowns prim-knowns imports mutated simples unsafe-mode?
(let ([k (infer-known rhs v id knowns prim-knowns imports mutated simples unsafe-mode?
#:primitives primitives
#:optimize-inline? optimize?)])
(if k

View File

@ -12,19 +12,22 @@
"optimize.rkt")
(provide infer-known
can-improve-infer-known?
lambda?)
;; For definitions, it's useful to infer `a-known-constant` to reflect
;; that the variable will get a value without referencing anything
;; too early.
(define (infer-known rhs defn rec? id knowns prim-knowns imports mutated simples unsafe-mode?
;; too early. If `post-schemify?`, then `rhs` has been schemified.
(define (infer-known rhs defn id knowns prim-knowns imports mutated simples unsafe-mode?
#:primitives [primitives #hasheq()] ; for `optimize-inline?` mode
#:optimize-inline? [optimize-inline? #f])
#:optimize-inline? [optimize-inline? #f]
#:post-schemify? [post-schemify? #f])
(cond
[(lambda? rhs)
(define-values (lam inlinable?) (extract-lambda rhs))
(define arity-mask (lambda-arity-mask lam))
(if (and inlinable?
(not post-schemify?)
(or (can-inline? lam)
(wrap-property defn 'compiler-hint:cross-module-inline)))
(let ([lam (if optimize-inline?
@ -72,39 +75,55 @@
;; ----------------------------------------
;; Recognize forms that produce plain procedures
(define (can-improve-infer-known? k)
(or (not k)
(eq? k a-known-constant)))
;; ----------------------------------------
;; Recognize forms that produce plain procedures; expression can be
;; pre- or post-schemify
(define (lambda? v #:simple? [simple? #f])
(match v
[`(lambda . ,_) #t]
[`(case-lambda . ,_) #t]
[`(let-values ([(,id) ,rhs]) ,body) (or (and (wrap-eq? id body) (lambda? rhs))
(lambda? body))]
[`(letrec-values ([(,id) ,rhs]) ,body) (or (and (wrap-eq? id body) (lambda? rhs))
(lambda? body))]
[`(let-values ([(,id) ,rhs]) ,body) (let-lambda? id rhs body)]
[`(letrec-values ([(,id) ,rhs]) ,body) (let-lambda? id rhs body)]
[`(let ([,id ,rhs]) ,body) (let-lambda? id rhs body)]
[`(letrec* ([,id ,rhs]) ,body) (let-lambda? id rhs body)]
[`(let-values ,_ ,body) (and (not simple?) (lambda? body))]
[`(letrec-values ,_ ,body) (and (not simple?) (lambda? body))]
[`(begin ,body) (lambda? body)]
[`(values ,body) (lambda? body)]
[`,_ #f]))
;; Extract procedure from from forms that produce plain procedures
(define (let-lambda? id rhs body)
(or (and (wrap-eq? id body) (lambda? rhs))
(lambda? body)))
;; Extract procedure from a form on which `lambda?` produces true
(define (extract-lambda v)
(match v
[`(lambda . ,_) (values v #t)]
[`(case-lambda . ,_) (values v #t)]
[`(let-values ([(,id) ,rhs]) ,body)
(if (wrap-eq? id body)
(extract-lambda rhs)
(extract-lambda* body))]
[`(letrec-values ([(,id) ,rhs]) ,body)
(if (wrap-eq? id body)
(extract-lambda* rhs)
(extract-lambda* body))]
[`(let-values ([(,id) ,rhs]) ,body) (extract-let-lambda #f id rhs body)]
[`(letrec-values ([(,id) ,rhs]) ,body) (extract-let-lambda #t id rhs body)]
[`(let ([,id ,rhs]) ,body) (extract-let-lambda #f id rhs body)]
[`(letrec* ([,id ,rhs]) ,body) (extract-let-lambda #t id rhs body)]
[`(let-values ,_ ,body) (extract-lambda* body)]
[`(letrec-values ,_ ,body) (extract-lambda* body)]
[`(let ,_ ,body) (extract-lambda* body)]
[`(letrec* ,_ ,body) (extract-lambda* body)]
[`(begin ,body) (extract-lambda body)]
[`(values ,body) (extract-lambda body)]))
(define (extract-let-lambda rec? id rhs body)
(if (wrap-eq? id body)
(if rec?
(extract-lambda* rhs)
(extract-lambda rhs))
(extract-lambda* body)))
(define (extract-lambda* v)
(define-values (lam inlinable?) (extract-lambda v))
(values lam #f))

View File

@ -37,7 +37,14 @@
(cond
[(zero? size) 0]
[(wrap-pair? v)
(loop (wrap-cdr v) (loop (wrap-car v) size))]
(cond
[(eq? (unwrap (wrap-car v)) 'quote)
;; don't copy quoted values other than symbols
(if (symbol? (unwrap (wrap-car (wrap-cdr v))))
(sub1 size)
0)]
[else
(loop (wrap-cdr v) (loop (wrap-car v) size))])]
[else (sub1 size)]))))
;; ----------------------------------------

View File

@ -14,25 +14,33 @@
(namespace-require 'racket/unsafe/ops)
(namespace-require 'racket/flonum)
(namespace-require 'racket/fixnum))
(define primitives
(define base-primitives
(for/hasheq ([s (in-list (namespace-mapped-symbols ns))]
#:when (with-handlers ([exn:fail? (lambda (x) #f)])
(procedure? (eval s ns))))
(values s (eval s ns))))
(define primitives (let* ([ht base-primitives]
[ht (hash-set ht 'eof eof)]
[ht (hash-set ht 'null null)])
ht))
(values
(for/hasheq ([(s v) (in-hash primitives)])
(define a (procedure-arity-mask v))
(values s (case s
[(+ - * /)
(known-procedure/folding a)]
[(fx+ fxlshift)
(known-procedure/folding/limited a 'fixnum)]
[(expt arithmetic-shift)
(known-procedure/folding/limited a 'expt)]
[(unsafe-fx+)
(known-procedure/pure/folding-unsafe a 'fx+)]
[else
(known-procedure a)])))
(cond
[(procedure? v)
(define a (procedure-arity-mask v))
(values s (case s
[(+ - * / integer->char char->integer void)
(known-procedure/folding a)]
[(fx+ fxlshift)
(known-procedure/folding/limited a 'fixnum)]
[(expt arithmetic-shift)
(known-procedure/folding/limited a 'expt)]
[(unsafe-fx+)
(known-procedure/pure/folding-unsafe a 'fx+)]
[else
(known-procedure a)]))]
[else
(values s (known-literal v))]))
primitives)))
(define (wrap p)
@ -60,7 +68,7 @@
(define-values (schemified importss exports import-keys imports-abis exports-info)
(schemify-linklet `(linklet
()
(x y [z ext-z] w)
(x y [z ext-z] w c1 c2)
.
,(map
wrap
@ -77,8 +85,13 @@
b
(arithmetic-shift 3 1000)
(fx+ 4 5) (fx+ 4 (expt 2 40)) (fx* (fxlshift 1 20) (fxlshift 1 20))
(unsafe-fx+ 4 5) (unsafe-fx+ 4 (expt 2 40))))
(define-values (done) (z)))))
(unsafe-fx+ 4 5) (unsafe-fx+ 4 (expt 2 40))
(integer->char 48)
(char->integer '#\1)
(void (void) eof-object null)))
(define-values (done) (z))
(define-values (call) (lambda () (values 'c1 'c2)))
(define-values (c1 c2) (call)))))
#;
(call-with-input-file "regexp.rktl" read)
#t ; serializable

View File

@ -213,11 +213,13 @@
(append (for/list ([(int-id ex) (in-hash extra-variables)])
`(define ,(export-id ex) (make-internal-variable 'int-id)))
l))
;; Mutated to communicate the final `knowns`
(define final-knowns knowns)
;; While schemifying, add calls to install exported values in to the
;; corresponding exported `variable` records, but delay those
;; installs to the end, if possible
(define schemified
(let loop ([l l] [in-mut-l l] [accum-exprs null] [accum-ids null])
(let loop ([l l] [in-mut-l l] [accum-exprs null] [accum-ids null] [knowns knowns])
(define mut-l (update-mutated-state! l in-mut-l mutated))
(define (make-set-variables)
(for/list ([id (in-wrap-list accum-ids)]
@ -229,9 +231,10 @@
(for/list ([e (in-list (reverse es))])
(make-expr-defn e))))
(cond
[(null? l)
;; Finish by making sure that all pending variables in `accum-ids` are
;; moved into their `variable` records:
[(null? l)
(set! final-knowns knowns)
;; Finish by making sure that all pending variables in `accum-ids` are
;; moved into their `variable` records:
(define set-vars (make-set-variables))
(cond
[(null? set-vars)
@ -252,17 +255,33 @@
;; continuation or return multiple times, we can generate a
;; simple definition:
(define (finish-definition ids [accum-exprs accum-exprs] [accum-ids accum-ids]
#:knowns [knowns knowns]
#:schemified [schemified schemified]
#:k [k #f])
#:next-k [next-k #f])
;; Maybe schemify made a known shape apparent:
(define next-knowns
(cond
[(and (pair? ids)
(null? (cdr ids))
(can-improve-infer-known? (hash-ref knowns (unwrap (car ids)) #f)))
(define id (car ids))
(define k (match schemified
[`(define ,id ,rhs)
(infer-known rhs #f id knowns prim-knowns imports mutated simples unsafe-mode?
#:post-schemify? #t)]))
(if k
(hash-set knowns (unwrap id) k)
knowns)]
[else knowns]))
(append
(make-expr-defns accum-exprs)
(cons
schemified
(let id-loop ([ids ids] [accum-exprs null] [accum-ids accum-ids])
(cond
[(wrap-null? ids) (if k
(k accum-exprs accum-ids)
(loop (wrap-cdr l) mut-l accum-exprs accum-ids))]
[(wrap-null? ids) (if next-k
(next-k accum-exprs accum-ids next-knowns)
(loop (wrap-cdr l) mut-l accum-exprs accum-ids next-knowns))]
[(or (or for-jitify? for-cify?)
(via-variable-mutated-state? (hash-ref mutated (unwrap (wrap-car ids)) #f)))
(define id (unwrap (wrap-car ids)))
@ -291,7 +310,7 @@
[no-prompt?
(cons
schemified
(loop (wrap-cdr l) mut-l null ids))]
(loop (wrap-cdr l) mut-l null ids knowns))]
[else
(define expr
`(call-with-module-prompt
@ -308,7 +327,7 @@
(if for-jitify?
expr
(make-expr-defn expr))
(append defns (loop (wrap-cdr l) mut-l null null)))])))
(append defns (loop (wrap-cdr l) mut-l null null knowns)))])))
;; Dispatch on the schemified form, distinguishing definitions
;; from expressions:
(match schemified
@ -330,16 +349,17 @@
;; too early:
(for/and ([rhs (in-list rhss)])
(simple? rhs prim-knowns knowns imports mutated simples)))
(let values-loop ([ids ids] [rhss rhss] [accum-exprs accum-exprs] [accum-ids accum-ids])
(let values-loop ([ids ids] [rhss rhss] [accum-exprs accum-exprs] [accum-ids accum-ids] [knowns knowns])
(cond
[(null? ids) (loop (wrap-cdr l) mut-l accum-exprs accum-ids)]
[(null? ids) (loop (wrap-cdr l) mut-l accum-exprs accum-ids knowns)]
[else
(define id (car ids))
(define rhs (car rhss))
(finish-definition (list id) accum-exprs accum-ids
#:schemified `(define ,id ,rhs)
#:k (lambda (accum-exprs accum-ids)
(values-loop (cdr ids) (cdr rhss) accum-exprs accum-ids)))]))
#:knowns knowns
#:schemified `(define ,id ,rhs)
#:next-k (lambda (accum-exprs accum-ids knowns)
(values-loop (cdr ids) (cdr rhss) accum-exprs accum-ids knowns)))]))
(finish-definition ids))]
[`,_ (finish-definition ids)])]
[else
@ -355,17 +375,17 @@
[`,_
(cond
[(simple? #:pure? #f schemified prim-knowns knowns imports mutated simples)
(loop (wrap-cdr l) mut-l (cons schemified accum-exprs) accum-ids)]
(loop (wrap-cdr l) mut-l (cons schemified accum-exprs) accum-ids knowns)]
[else
;; In case `schemified` triggers an error, sync exported variables
(define set-vars (make-set-variables))
(define expr (if no-prompt?
schemified
`(call-with-module-prompt (lambda () ,schemified))))
(loop (wrap-cdr l) mut-l (cons expr (append set-vars accum-exprs)) null)])])])])))
(loop (wrap-cdr l) mut-l (cons expr (append set-vars accum-exprs)) null knowns)])])])])))
;; Return both schemified and known-binding information, where
;; the later is used for cross-linklet optimization
(values (add-extra-variables schemified) knowns mutated))
(values (add-extra-variables schemified) final-knowns mutated))
(define (make-set-variable id exports knowns mutated [extra-variables #f])
(define int-id (unwrap id))
@ -451,13 +471,15 @@
(define new-knowns
(for/fold ([knowns knowns]) ([id (in-list ids)]
[rhs (in-list rhss)])
(define k (infer-known rhs #f #f id knowns prim-knowns imports mutated simples unsafe-mode?))
(define k (infer-known rhs #f id knowns prim-knowns imports mutated simples unsafe-mode?))
(if k
(hash-set knowns (unwrap id) k)
knowns)))
(define (merely-a-copy? id)
(define u-id (unwrap id))
(and (known-copy? (hash-ref new-knowns u-id #f))
(define k (hash-ref new-knowns u-id #f))
(and (or (known-copy? k)
(known-literal? k))
(simple-mutated-state? (hash-ref mutated u-id #f))))
(left-to-right/let (for/list ([id (in-list ids)]
#:unless (merely-a-copy? id))
@ -492,7 +514,7 @@
(define-values (rhs-knowns body-knowns)
(for/fold ([rhs-knowns knowns] [body-knowns knowns]) ([id (in-list ids)]
[rhs (in-list rhss)])
(define k (infer-known rhs #f #t id knowns prim-knowns imports mutated simples unsafe-mode?))
(define k (infer-known rhs #f id knowns prim-knowns imports mutated simples unsafe-mode?))
(define u-id (unwrap id))
(cond
[(too-early-mutated-state? (hash-ref mutated u-id #f))