cs: move letrec conversion to schemify

Instead of having schemify generate `letrec*` and convert as needed
through a Chez Scheme macro, have schemify perform any necessary
conversion to get the right use-before-definition error messages and
`call/cc` interaction.

This change improves the conversion, since schemify has more
information about bindings, but it also avoids sending Racket terms
through a macro-generating macro at the Chez Scheme level. Avoiding
the macro-generating macro avoids a kind of leak in Chez Scheme, where
a gensym used in a template may become ineligible for GC due to the
way `free-id=?` may both reify the gensym's unique name and attach a
property to the gensym.
This commit is contained in:
Matthew Flatt 2019-05-26 21:00:37 -06:00
parent fe708871bd
commit 9d3a49f265
16 changed files with 293 additions and 219 deletions

View File

@ -245,7 +245,6 @@ RUMBLE_SRCS = rumble/define.ss \
rumble/layout.ss \
rumble/check.ss \
rumble/syntax-rule.ss \
rumble/letrec.ss \
rumble/constant.ss \
rumble/hash-code.ss \
rumble/struct.ss \

View File

@ -127,7 +127,7 @@
(time
(schemify-body bodys/constants-lifted prim-knowns #hasheq() #hasheq() for-cify? unsafe-mode? #t)))
(printf "Lift...\n")
;; Lift functions to aviod closure creation:
;; Lift functions to avoid closure creation:
(define lifted-body
(time
(lift-in-schemified-body body)))

View File

@ -10,21 +10,6 @@
[(and (eq? a (car v))
(eq? d (cdr v)))
(values v v)]
[(and (eq? stripped-a 'letrec*)
(pair? (cdr v)))
(let ([names (let loop ([clauses (cadr v)])
(cond
[(null? clauses) '()]
[else
(let ([id (caar clauses)])
(cons (or (and (correlated? id)
(correlated-property id 'undefined-error-name))
(if (correlated? id)
(correlated-e id)
id))
(loop (cdr clauses))))]))])
(values (list* 'letrec*/names names d)
(list* 'letrec*/names names stripped-d)))]
[else (values (cons a d)
(cons stripped-a stripped-d))]))]
[(correlated? v) (let-values ([(e stripped-e) (correlated->annotation* (correlated-e v))])

View File

@ -7,8 +7,6 @@
begin0
$value
letrec*/names
dynamic-wind
call-with-current-continuation
call-with-composable-continuation
@ -723,7 +721,6 @@
(include "rumble/virtual-register.ss")
(include "rumble/layout.ss")
(include "rumble/begin0.ss")
(include "rumble/letrec.ss")
(include "rumble/syntax-rule.ss")
(include "rumble/value.ss")
(include "rumble/lock.ss")

View File

@ -1,47 +0,0 @@
(meta define no-early-reference?
(lambda (stx ids)
(cond
[(#%identifier? stx)
(not (#%ormap (lambda (id) (free-identifier=? id stx)) ids))]
[(let ([d (syntax->datum stx)])
(or (number? d) (boolean? d) (string? d) (bytevector? d)))
#t]
[else
(syntax-case stx (quote |#%name| lambda case-lambda)
[(quote _) #t]
[(|#%name| _ exp) (no-early-reference? #'exp ids)]
[(lambda . _) #t]
[(case-lambda . _) #t]
[_ #f])])))
(meta define no-early-references?
(lambda (rhss ids)
(cond
[(null? rhss) #t]
[else (and (no-early-reference? (car rhss) ids)
(no-early-references? (cdr rhss) (cdr ids)))])))
;; Like `letrec*`, but makes use-before-definition checks explicit so
;; that a source name is included in the error messages. Also, the
;; expansion allows `call/cc`-based capture and multiple return on the
;; right-hand side.
(define-syntax (letrec*/names stx)
(syntax-case stx ()
[(_ (name ...) ([id rhs] ...) body ...)
(cond
[(no-early-references? #'(rhs ...) #'(id ...))
#'(letrec* ([id rhs] ...) body ...)]
[else
(with-syntax ([(tmp-id ...) (generate-temporaries #'(id ...))])
#'(let ([tmp-id unsafe-undefined] ...)
(let-syntax ([id (identifier-syntax
[id (check-not-unsafe-undefined tmp-id 'name)]
[(set! id exp)
(let ([id exp])
(check-not-unsafe-undefined/assign tmp-id 'name)
(set! tmp-id id))])]
...)
(set! tmp-id rhs)
...
(let ()
body ...))))])]))

View File

@ -11,7 +11,7 @@
;; Record top-level functions and structure types, and returns
;; (values knowns struct-type-info-or-#f)
(define (find-definitions v prim-knowns knowns imports mutated unsafe-mode?
(define (find-definitions v prim-knowns knowns imports mutated simples unsafe-mode?
#:optimize? optimize?)
(match v
[`(define-values (,id) ,orig-rhs)
@ -19,7 +19,7 @@
(optimize orig-rhs prim-knowns knowns imports mutated)
orig-rhs))
(values
(let ([k (infer-known rhs v #t id knowns prim-knowns imports mutated unsafe-mode?
(let ([k (infer-known rhs v #t id knowns prim-knowns imports mutated simples unsafe-mode?
#:optimize-inline? optimize?)])
(if k
(hash-set knowns (unwrap id) k)
@ -118,7 +118,7 @@
[rhs (in-list rhss)])
(define-values (new-knowns info)
(find-definitions `(define-values (,id) ,rhs)
prim-knowns knowns imports mutated unsafe-mode?
prim-knowns knowns imports mutated simples unsafe-mode?
#:optimize? optimize?))
new-knowns)
#f)]

View File

@ -16,7 +16,7 @@
;; 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 unsafe-mode?
(define (infer-known rhs defn rec? id knowns prim-knowns imports mutated simples unsafe-mode?
#:optimize-inline? [optimize-inline? #f])
(cond
[(lambda? rhs)
@ -62,7 +62,7 @@
[(pthread-parameter? rhs prim-knowns knowns mutated)
(known-procedure 3)]
[(and defn
(simple? rhs prim-knowns knowns imports mutated))
(simple? rhs prim-knowns knowns imports mutated simples))
a-known-constant]
[else #f]))

View File

@ -14,7 +14,7 @@
;; expressions that have no shadowing (and introduce
;; shadowing here)
(define (left-to-right/let ids rhss bodys
prim-knowns knowns imports mutated)
prim-knowns knowns imports mutated simples)
(cond
[(null? ids) (if (null? (cdr bodys))
(car bodys)
@ -28,7 +28,7 @@
(define id (car ids))
(define rhs (car rhss))
(if (and all-simple?
(simple? rhs prim-knowns knowns imports mutated))
(simple? rhs prim-knowns knowns imports mutated simples))
`(let ([,id ,rhs])
. ,bodys)
`(let ([,id ,rhs])
@ -41,7 +41,7 @@
,(loop (cdr ids)
(cdr rhss)
(and all-simple?
(simple? rhs prim-knowns knowns imports mutated))
(simple? rhs prim-knowns knowns imports mutated simples))
(cons `[,id ,id] binds)))]))]))
;; Convert a `let-values` to nested `let-values`es to
@ -75,7 +75,7 @@
;; Convert an application to enforce left-to-right
;; evaluation order
(define (left-to-right/app rator rands plain-app? for-cify?
prim-knowns knowns imports mutated)
prim-knowns knowns imports mutated simples)
(cond
[for-cify? (cons rator rands)]
[else
@ -98,7 +98,7 @@
(if plain-app?
app
`(|#%app| . ,app)))]
[(simple? (car l) prim-knowns knowns imports mutated)
[(simple? (car l) prim-knowns knowns imports mutated simples)
(loop (cdr l) (cons (car l) accum) pending-non-simple pending-id)]
[pending-non-simple
`(let ([,pending-id ,pending-non-simple])

View File

@ -1,9 +1,12 @@
#lang racket/base
(require "wrap.rkt"
"infer-known.rkt")
"match.rkt"
"infer-known.rkt"
"mutated-state.rkt")
(provide letrec-splitable-values-binding?
letrec-split-values-binding)
letrec-split-values-binding
letrec-conversion)
;; Detect binding of lambdas that were probably generated from an
;; R[56]RS program
@ -24,3 +27,24 @@
`[(,id) ,rhs])
. ,bodys))
(define (letrec-conversion ids mutated for-cify? e)
(define need-convert?
(and (not for-cify?)
(let loop ([ids ids])
(cond
[(symbol? ids)
(needs-letrec-convert-mutated-state? (hash-ref mutated ids #f))]
[(wrap? ids) (loop (unwrap ids))]
[(pair? ids) (or (loop (car ids))
(loop (cdr ids)))]
[else #f]))))
(if need-convert?
(match e
[`(,_ ([,ids ,rhss] ...) . ,body)
`(let ,(for/list ([id (in-list ids)])
`[,id unsafe-undefined])
,@(for/list ([id (in-list ids)]
[rhs (in-list rhss)])
`(set! ,id ,rhs))
. ,body)])
e))

View File

@ -12,7 +12,7 @@
schemify-body
(all-from-out "known.rkt")
lift-in-schemified-linklet
lift-in-schemified-body

View File

@ -5,11 +5,17 @@
;;
;; * 'set!ed - the identifier is `set!`ed
;;
;; * 'set!ed-too-early - the identifier is `set!`ed potentially
;; before it is initialized
;;
;; * 'implicitly-set!ed - the `letrec`-bound identifier maybe be
;; implicitly `set!`ed via `call/cc`
;;
;; * 'too-early - the identifier may be referenced before it is
;; defined
;;
;; * 'too-early/ready - a variant of 'too-early where the variable
;; is now definitely ready
;; is now definitely ready, used only for top levels
;;
;; * 'not-ready - the identifier's value is not yet ready, so a
;; reference transitions to 'too-early
@ -24,16 +30,24 @@
;;
;; * #f (not mapped) - defined and never `set!`ed
;;
;; By the end of the `mutated-in-body` pass, only 'set!ed, 'too-early,
;; 'not-ready (for exported but not defined) and #f are possible for
;; identifiers that are reachable by evaluation.
;; By the end of the `mutated-in-body` pass, only 'set!ed,
;; 'set!ed-too-early, 'implicitly-set!ed, 'too-early,
;; 'too-early/ready, 'not-ready (for exported but not defined) and #f
;; are possible for identifiers that are reachable by evaluation.
(provide delayed-mutated-state?
(provide too-early
delayed-mutated-state?
simple-mutated-state?
not-ready-mutated-state?
too-early-mutated-state?
too-early-mutated-state-name
needs-letrec-convert-mutated-state?
via-variable-mutated-state?
set!ed-mutated-state?)
set!ed-mutated-state?
state->set!ed-state)
;; Used for `letrec` bindings to record a name:
(struct too-early (name set!ed?))
(define (delayed-mutated-state? v) (procedure? v))
@ -46,18 +60,44 @@
(eq? v 'not-ready))
(define (too-early-mutated-state? v)
(eq? v 'too-early))
(or (eq? v 'too-early)
(eq? v 'set!ed-too-early)
(too-early? v)))
;; When referecing an exported identifier, we need to consistently go
(define (too-early-mutated-state-name v default-sym)
(if (too-early? v)
(too-early-name v)
default-sym))
(define (needs-letrec-convert-mutated-state? v)
(or (too-early? v)
(eq? v 'too-early)
(eq? v 'too-early/ready)
(eq? v 'implicitly-set!ed)))
;; When referencing an exported identifier, we need to consistently go
;; through a `variable` record when it can be `set!`ed or is not yet
;; ready (as indicated by 'too-early, which is changed to 'too-eary/ready
;; as the variable becomes ready)
(define (via-variable-mutated-state? v)
(or (eq? v 'set!ed)
(eq? v 'undefined)
(eq? v 'too-early)))
(eq? v 'too-early)
(eq? v 'set!ed-too-early)))
;; At the end of a linklet, known-value information is reliable unless
;; the identifier is mutated
;; the identifier is explicitly mutated
(define (set!ed-mutated-state? v)
(eq? v 'set!ed))
(or (eq? v 'set!ed)
(eq? v 'set!ed-too-early)
(and (too-early? v)
(too-early-set!ed? v))))
(define (state->set!ed-state v)
(cond
[(too-early? v)
(struct-copy too-early v [set!ed? #t])]
[(eq? v 'not-ready) 'set!ed-too-early]
[(too-early-mutated-state? v) 'set!ed-too-early]
[(eq? v 'implicitly-set!ed) v]
[else 'set!ed]))

View File

@ -21,7 +21,10 @@
;; definition of an identifier, because that will abort the enclosing
;; linklet.
(define (mutated-in-body l exports prim-knowns knowns imports unsafe-mode? enforce-constant?)
;; This pass is also responsible for recording when a letrec binding
;; must be mutated implicitly via `call/cc`.
(define (mutated-in-body l exports prim-knowns knowns imports simples unsafe-mode? enforce-constant?)
;; Find all `set!`ed variables, and also record all bindings
;; that might be used too early
(define mutated (make-hasheq))
@ -50,7 +53,7 @@
;; that information is correct, because it dynamically precedes
;; the `set!`
(define-values (knowns info)
(find-definitions form prim-knowns prev-knowns imports mutated unsafe-mode?
(find-definitions form prim-knowns prev-knowns imports mutated simples unsafe-mode?
#:optimize? #f))
(match form
[`(define-values (,ids ...) ,rhs)
@ -60,10 +63,10 @@
(for ([e (in-list (struct-type-info-rest info))]
[pos (in-naturals)])
(unless (and (= pos struct-type-info-rest-properties-list-pos)
(pure-properties-list? e prim-knowns knowns imports mutated))
(find-mutated! e ids prim-knowns knowns imports mutated)))]
(pure-properties-list? e prim-knowns knowns imports mutated simples))
(find-mutated! e ids prim-knowns knowns imports mutated simples)))]
[else
(find-mutated! rhs ids prim-knowns knowns imports mutated)])
(find-mutated! rhs ids prim-knowns knowns imports mutated simples)])
;; For any among `ids` that didn't get a delay and wasn't used
;; too early, the variable is now ready, so remove from
;; `mutated`:
@ -72,7 +75,7 @@
(when (eq? 'not-ready (hash-ref mutated id #f))
(hash-remove! mutated id))))]
[`,_
(find-mutated! form #f prim-knowns knowns imports mutated)])
(find-mutated! form #f prim-knowns knowns imports mutated simples)])
knowns)
;; For definitions that are not yet used, force delays:
(for ([form (in-list l)])
@ -91,7 +94,7 @@
;; Schemify `let-values` to `let`, etc., and
;; reorganize struct bindings.
(define (find-mutated! v ids prim-knowns knowns imports mutated)
(define (find-mutated! v ids prim-knowns knowns imports mutated simples)
(define (delay! ids thunk)
(define done? #f)
(define force (lambda () (unless done?
@ -135,14 +138,39 @@
(for* ([ids (in-list idss)]
[id (in-wrap-list ids)])
(hash-set! mutated (unwrap id) 'not-ready))
(for ([ids (in-list idss)]
[rhs (in-list rhss)])
(for/fold ([maybe-cc? #f]) ([ids (in-list idss)]
[rhs (in-list rhss)])
(find-mutated! rhs (unwrap-list ids))
(define new-maybe-cc? (or maybe-cc?
(not (simple? rhs prim-knowns knowns imports mutated simples
#:pure? #f))))
;; Each `id` in `ids` is now ready (but might also hold a delay):
(for ([id (in-wrap-list ids)])
(let ([id (unwrap id)])
(when (eq? 'not-ready (hash-ref mutated id))
(hash-remove! mutated id)))))
(let ([u-id (unwrap id)])
(define state (hash-ref mutated u-id))
(define (add-too-early-name!)
(cond
[(and (eq? 'too-early state)
(wrap-property id 'undefined-error-name))
=> (lambda (name)
(hash-set! mutated u-id (too-early name #f)))]
[(and (eq? 'set!ed-too-early state)
(wrap-property id 'undefined-error-name))
=> (lambda (name)
(hash-set! mutated u-id (too-early name #t)))]))
(cond
[new-maybe-cc?
(cond
[(or (eq? 'not-ready state)
(delayed-mutated-state? state))
(hash-set! mutated u-id 'implicitly-set!ed)]
[else (add-too-early-name!)])
(when (delayed-mutated-state? state)
(state))]
[(eq? 'not-ready state)
(hash-remove! mutated u-id)]
[else (add-too-early-name!)])))
new-maybe-cc?)
(find-mutated!* bodys ids)])]
[`(if ,tst ,thn ,els)
(find-mutated! tst #f)
@ -160,7 +188,7 @@
[`(set! ,id ,rhs)
(let ([id (unwrap id)])
(define old-state (hash-ref mutated id #f))
(hash-set! mutated id 'set!ed)
(hash-set! mutated id (state->set!ed-state old-state))
(when (delayed-mutated-state? old-state)
(old-state)))
(find-mutated! rhs #f)]
@ -174,7 +202,7 @@
(and (known-constructor? v)
(bitwise-bit-set? (known-procedure-arity-mask v) (length exps))))
(for/and ([exp (in-list exps)])
(simple? exp prim-knowns knowns imports mutated)))))
(simple? exp prim-knowns knowns imports mutated simples)))))
;; Can delay construction
(delay! ids (lambda () (find-mutated!* exps #f)))]
[else
@ -216,7 +244,9 @@
[(lambda? rhs #:simple? #t)
(for ([id (in-list ids)])
(define u-id (unwrap id))
(when (too-early-mutated-state? (hash-ref mutated u-id #f))
(define state (hash-ref mutated u-id #f))
(when (and (too-early-mutated-state? state)
(not set!ed-mutated-state? state))
(hash-set! mutated u-id 'too-early/ready)))
(loop (wrap-cdr mut-l))]
[else mut-l])]

View File

@ -191,15 +191,17 @@
(define (schemify-body* l prim-knowns imports exports
for-jitify? allow-set!-undefined? add-import!
for-cify? unsafe-mode? enforce-constant? allow-inline? no-prompt?)
;; Keep simple checking efficient by caching results
(define simples (make-hasheq))
;; Various conversion steps need information about mutated variables,
;; where "mutated" here includes visible implicit mutation, such as
;; a variable that might be used before it is defined:
(define mutated (mutated-in-body l exports prim-knowns (hasheq) imports unsafe-mode? enforce-constant?))
(define mutated (mutated-in-body l exports prim-knowns (hasheq) imports simples unsafe-mode? enforce-constant?))
;; Make another pass to gather known-binding information:
(define knowns
(for/fold ([knowns (hasheq)]) ([form (in-list l)])
(define-values (new-knowns info)
(find-definitions form prim-knowns knowns imports mutated unsafe-mode?
(find-definitions form prim-knowns knowns imports mutated simples unsafe-mode?
#:optimize? #t))
new-knowns))
;; For non-exported definitions, we may need to create some variables
@ -239,7 +241,7 @@
[else
(define form (car l))
(define schemified (schemify form
prim-knowns knowns mutated imports exports
prim-knowns knowns mutated imports exports simples
allow-set!-undefined?
add-import!
for-cify? for-jitify?
@ -307,13 +309,13 @@
(match schemified
[`(define ,id ,rhs)
(cond
[(simple? #:pure? #f rhs prim-knowns knowns imports mutated)
[(simple? #:pure? #f rhs prim-knowns knowns imports mutated simples)
(finish-definition (list id))]
[else
(finish-wrapped-definition (list id) rhs)])]
[`(define-values ,ids ,rhs)
(cond
[(simple? #:pure? #f rhs prim-knowns knowns imports mutated)
[(simple? #:pure? #f rhs prim-knowns knowns imports mutated simples)
(finish-definition ids)]
[else
(finish-wrapped-definition ids rhs)])]
@ -329,7 +331,7 @@
(finish-definition ids (append set-vars accum-exprs) null)]
[`,_
(cond
[(simple? #:pure? #f schemified prim-knowns knowns imports mutated)
[(simple? #:pure? #f schemified prim-knowns knowns imports mutated simples)
(loop (wrap-cdr l) mut-l (cons schemified accum-exprs) accum-ids)]
[else
;; In case `schemified` triggers an error, sync exported variables
@ -376,9 +378,12 @@
;; ----------------------------------------
;; Schemify `let-values` to `let`, etc., and
;; reorganize struct bindings.
(define (schemify v prim-knowns knowns mutated imports exports allow-set!-undefined? add-import!
;; Schemify `let-values` to `let`, etc., and reorganize struct bindings.
;;
;; Non-simple `mutated` state overrides bindings in `knowns`; a
;; a 'too-early state in `mutated` for a `letrec`-bound variable can be
;; effectively canceled with a mapping in `knowns`.
(define (schemify v prim-knowns knowns mutated imports exports simples allow-set!-undefined? add-import!
for-cify? for-jitify? unsafe-mode? allow-inline? no-prompt?)
(let schemify/knowns ([knowns knowns] [inline-fuel init-inline-fuel] [v v])
(define (schemify v)
@ -437,7 +442,7 @@
(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 unsafe-mode?))
(define k (infer-known rhs #f #f id knowns prim-knowns imports mutated simples unsafe-mode?))
(if k
(hash-set knowns (unwrap id) k)
knowns)))
@ -454,11 +459,11 @@
(schemify rhs))
(for/list ([body (in-list bodys)])
(schemify/knowns new-knowns inline-fuel body))
prim-knowns knowns imports mutated)]
prim-knowns knowns imports mutated simples)]
[`(let-values ([() (begin ,rhss ... (values))]) ,bodys ...)
`(begin ,@(schemify-body rhss) ,@(schemify-body bodys))]
[`(let-values ([,idss ,rhss] ...) ,bodys ...)
(or (struct-convert-local v prim-knowns knowns imports mutated
(or (struct-convert-local v prim-knowns knowns imports mutated simples
(lambda (v knowns) (schemify/knowns knowns inline-fuel v))
#:unsafe-mode? unsafe-mode?)
(left-to-right/let-values idss
@ -475,21 +480,26 @@
;; special case of splitable values:
(schemify `(letrec-values ([(,id) ,rhs]) . ,bodys))]
[`(letrec-values ([(,ids) ,rhss] ...) ,bodys ...)
(define new-knowns
(for/fold ([knowns knowns]) ([id (in-list ids)]
[rhs (in-list rhss)])
(define k (infer-known rhs #f #t id knowns prim-knowns imports mutated unsafe-mode?))
(if k
(hash-set knowns (unwrap id) k)
knowns)))
`(letrec* ,(for/list ([id (in-list ids)]
[rhs (in-list rhss)])
`[,id ,(schemify/knowns new-knowns inline-fuel rhs)])
,@(for/list ([body (in-list bodys)])
(schemify/knowns new-knowns inline-fuel body)))]
(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 u-id (unwrap id))
(cond
[(too-early-mutated-state? (hash-ref mutated u-id #f))
(values rhs-knowns (hash-set knowns u-id (or k a-known-constant)))]
[k (values (hash-set rhs-knowns u-id k) (hash-set body-knowns u-id k))]
[else (values rhs-knowns body-knowns)])))
(letrec-conversion
ids mutated for-cify?
`(letrec* ,(for/list ([id (in-list ids)]
[rhs (in-list rhss)])
`[,id ,(schemify/knowns rhs-knowns inline-fuel rhs)])
,@(for/list ([body (in-list bodys)])
(schemify/knowns body-knowns inline-fuel body))))]
[`(letrec-values ([,idss ,rhss] ...) ,bodys ...)
(cond
[(struct-convert-local v #:letrec? #t prim-knowns knowns imports mutated
[(struct-convert-local v #:letrec? #t prim-knowns knowns imports mutated simples
(lambda (v knowns) (schemify/knowns knowns inline-fuel v))
#:unsafe-mode? unsafe-mode?)
=> (lambda (form) form)]
@ -504,24 +514,26 @@
;; [id (vector-ref vec 0)]
;; ... ...)
;; ....)
`(letrec* ,(apply
append
(for/list ([ids (in-wrap-list idss)]
[rhs (in-list rhss)])
(let ([rhs (schemify rhs)])
(cond
[(null? ids)
`([,(gensym "lr")
,(make-let-values null rhs '(void) for-cify?)])]
[(and (pair? ids) (null? (cdr ids)))
`([,(car ids) ,rhs])]
[else
(define lr (gensym "lr"))
`([,lr ,(make-let-values ids rhs `(vector . ,ids) for-cify?)]
,@(for/list ([id (in-list ids)]
[pos (in-naturals)])
`[,id (unsafe-vector*-ref ,lr ,pos)]))]))))
,@(schemify-body bodys))])]
(letrec-conversion
idss mutated for-cify?
`(letrec* ,(apply
append
(for/list ([ids (in-wrap-list idss)]
[rhs (in-list rhss)])
(let ([rhs (schemify rhs)])
(cond
[(null? ids)
`([,(gensym "lr")
,(make-let-values null rhs '(void) for-cify?)])]
[(and (pair? ids) (null? (cdr ids)))
`([,(car ids) ,rhs])]
[else
(define lr (gensym "lr"))
`([,lr ,(make-let-values ids rhs `(vector . ,ids) for-cify?)]
,@(for/list ([id (in-list ids)]
[pos (in-naturals)])
`[,id (unsafe-vector*-ref ,lr ,pos)]))]))))
,@(schemify-body bodys)))])]
[`(if ,tst ,thn ,els)
`(if ,(schemify tst) ,(schemify thn) ,(schemify els))]
[`(with-continuation-mark ,key ,val ,body)
@ -535,9 +547,21 @@
[`(set! ,id ,rhs)
(define int-id (unwrap id))
(define ex (hash-ref exports int-id #f))
(if ex
`(,(if allow-set!-undefined? 'variable-set! 'variable-set!/check-undefined) ,(export-id ex) ,(schemify rhs) '#f)
`(set! ,id ,(schemify rhs)))]
(define new-rhs (schemify rhs))
(cond
[ex
`(,(if allow-set!-undefined? 'variable-set! 'variable-set!/check-undefined) ,(export-id ex) ,new-rhs '#f)]
[else
(define state (hash-ref mutated int-id #f))
(cond
[(and (too-early-mutated-state? state)
(not for-cify?))
(define tmp (gensym 'set))
`(let ([,tmp ,new-rhs])
(check-not-unsafe-undefined/assign ,id ',(too-early-mutated-state-name state int-id))
(set! ,id ,tmp))]
[else
`(set! ,id ,new-rhs)])])]
[`(variable-reference-constant? (#%variable-reference ,id))
(define u-id (unwrap id))
(cond
@ -585,7 +609,7 @@
(left-to-right/app 'equal?
(list exp1 exp2)
#t for-cify?
prim-knowns knowns imports mutated)]))]
prim-knowns knowns imports mutated simples)]))]
[`(call-with-values ,generator ,receiver)
(cond
[(and (lambda? generator)
@ -595,7 +619,7 @@
(left-to-right/app (if for-cify? 'call-with-values '#%call-with-values)
(list (schemify generator) (schemify receiver))
#t for-cify?
prim-knowns knowns imports mutated)])]
prim-knowns knowns imports mutated simples)])]
[`((letrec-values ,binds ,rator) ,rands ...)
(schemify `(letrec-values ,binds (,rator . ,rands)))]
[`(,rator ,exps ...)
@ -693,7 +717,7 @@
(left-to-right/app (car e)
(cdr e)
#t for-cify?
prim-knowns knowns imports mutated))]
prim-knowns knowns imports mutated simples))]
[(and (not for-cify?)
(known-field-accessor? k)
(inline-field-access k s-rator im args))
@ -707,14 +731,14 @@
(left-to-right/app (known-procedure/has-unsafe-alternate k)
args
#t for-cify?
prim-knowns knowns imports mutated)]
prim-knowns knowns imports mutated simples)]
[else
(define plain-app? (or (known-procedure? k)
(lambda? rator)))
(left-to-right/app s-rator
args
plain-app? for-cify?
prim-knowns knowns imports mutated)])))]
prim-knowns knowns imports mutated simples)])))]
[`,_
(let ([u-v (unwrap v)])
(cond
@ -722,38 +746,46 @@
v]
[(eq? u-v 'call-with-values)
'#%call-with-values]
[(and (via-variable-mutated-state? (hash-ref mutated u-v #f))
(hash-ref exports u-v #f))
=> (lambda (ex) `(variable-ref ,(export-id ex)))]
[(hash-ref imports u-v #f)
=> (lambda (im)
(define k (import-lookup im))
(if (known-constant? k)
;; Not boxed:
(cond
[(known-literal? k)
;; We'd normally leave this to `optimize`, but
;; need to handle it here before generating a
;; reference to the renamed identifier
(known-literal-expr k)]
[(and (known-copy? k)
(hash-ref prim-knowns (known-copy-id k) #f))
;; Directly reference primitive
(known-copy-id k)]
[else
(import-id im)])
;; Will be boxed, but won't be undefined (because the
;; module system won't link to an instance whose
;; definitions didn't complete):
`(variable-ref/no-check ,(import-id im))))]
[(hash-ref knowns u-v #f)
=> (lambda (k)
(cond
[(and (known-copy? k)
(simple-mutated-state? (hash-ref mutated u-v #f)))
(schemify (known-copy-id k))]
[else v]))]
[else v]))])))
[else
(define state (hash-ref mutated u-v #f))
(cond
[(and (via-variable-mutated-state? state)
(hash-ref exports u-v #f))
=> (lambda (ex) `(variable-ref ,(export-id ex)))]
[(hash-ref imports u-v #f)
=> (lambda (im)
(define k (import-lookup im))
(if (known-constant? k)
;; Not boxed:
(cond
[(known-literal? k)
;; We'd normally leave this to `optimize`, but
;; need to handle it here before generating a
;; reference to the renamed identifier
(known-literal-expr k)]
[(and (known-copy? k)
(hash-ref prim-knowns (known-copy-id k) #f))
;; Directly reference primitive
(known-copy-id k)]
[else
(import-id im)])
;; Will be boxed, but won't be undefined (because the
;; module system won't link to an instance whose
;; definitions didn't complete):
`(variable-ref/no-check ,(import-id im))))]
[(hash-ref knowns u-v #f)
=> (lambda (k)
(cond
[(and (known-copy? k)
(simple-mutated-state? (hash-ref mutated u-v #f)))
(schemify (known-copy-id k))]
[else v]))]
[(and (too-early-mutated-state? state)
(not for-cify?))
;; Note: we don't get to this case if `knowns` has
;; a mapping that says the variable is ready by now
`(check-not-unsafe-undefined ,v ',(too-early-mutated-state-name state u-v))]
[else v])]))])))
(optimize s-v prim-knowns knowns imports mutated))
(define (schemify-body l)

View File

@ -11,46 +11,60 @@
;; Check whether an expression is simple in the sense that its order
;; of evaluation isn't detectable. This function receives both
;; schemified and non-schemified expressions.
(define (simple? e prim-knowns knowns imports mutated
(define (simple? e prim-knowns knowns imports mutated simples
#:pure? [pure? #t])
(let simple? ([e e])
(define-syntax-rule (cached expr)
(let* ([c (hash-ref simples e '(unknown . unknown))]
[r (if pure? (car c) (cdr c))])
(if (eq? 'unknown r)
(let ([r expr])
(hash-set! simples e (if pure? (cons r (cdr c)) (cons (car c) r)))
r)
r)))
(match e
[`(lambda . ,_) #t]
[`(case-lambda . ,_) #t]
[`(quote . ,_) #t]
[`(#%variable-reference . ,_) #t]
[`(let-values ([,_ ,rhss] ...) ,body)
(and (for/and ([rhs (in-list rhss)])
(simple? rhs))
(simple? body))]
(cached
(and (for/and ([rhs (in-list rhss)])
(simple? rhs))
(simple? body)))]
[`(let ([,_ ,rhss] ...) ,body)
(and (for/and ([rhs (in-list rhss)])
(simple? rhs))
(simple? body))]
(cached
(and (for/and ([rhs (in-list rhss)])
(simple? rhs))
(simple? body)))]
[`(letrec-values ([(,idss ...) ,rhss] ...) ,body)
(and (for/and ([rhs (in-list rhss)])
(simple? rhs))
(simple? body))]
(cached
(and (for/and ([rhs (in-list rhss)])
(simple? rhs))
(simple? body)))]
[`(letrec* ([,ids ,rhss] ...) ,body)
(and (for/and ([rhs (in-list rhss)])
(simple? rhs))
(simple? body))]
(cached
(and (for/and ([rhs (in-list rhss)])
(simple? rhs))
(simple? body)))]
[`(begin ,es ...)
#:guard (not pure?)
(for/and ([e (in-list es)])
(simple? e))]
(cached
(for/and ([e (in-list es)])
(simple? e)))]
[`(,proc . ,args)
(let ([proc (unwrap proc)])
(and (symbol? proc)
(let ([v (or (hash-ref-either knowns imports proc)
(hash-ref prim-knowns proc #f))])
(and (if pure?
(known-procedure/pure? v)
(known-procedure/succeeds? v))
(bitwise-bit-set? (known-procedure-arity-mask v) (length args))))
(simple-mutated-state? (hash-ref mutated proc #f))
(for/and ([arg (in-list args)])
(simple? arg))))]
(cached
(let ([proc (unwrap proc)])
(and (symbol? proc)
(let ([v (or (hash-ref-either knowns imports proc)
(hash-ref prim-knowns proc #f))])
(and (if pure?
(known-procedure/pure? v)
(known-procedure/succeeds? v))
(bitwise-bit-set? (known-procedure-arity-mask v) (length args))))
(simple-mutated-state? (hash-ref mutated proc #f))
(for/and ([arg (in-list args)])
(simple? arg)))))]
[`,_
(let ([e (unwrap e)])
(or (and (symbol? e)

View File

@ -150,7 +150,7 @@
[`,_ #f]))
(define (struct-convert-local form #:letrec? [letrec? #f]
prim-knowns knowns imports mutated
prim-knowns knowns imports mutated simples
schemify
#:unsafe-mode? unsafe-mode?)
(match form
@ -164,7 +164,7 @@
(match new-seq
[`(begin . ,new-seq)
(define-values (new-knowns info)
(find-definitions defn prim-knowns knowns imports mutated unsafe-mode?
(find-definitions defn prim-knowns knowns imports mutated simples unsafe-mode?
#:optimize? #f))
(cond
[letrec?

View File

@ -87,7 +87,7 @@
;; Check whether `e` has the shape of a property list that uses only
;; properties where the property doesn't have a guard or won't invoke
;; a guarded procedure
(define (pure-properties-list? e prim-knowns knowns imports mutated)
(define (pure-properties-list? e prim-knowns knowns imports mutated simples)
(match e
[`(list (cons ,props ,vals) ...)
(for/and ([prop (in-list props)]
@ -96,7 +96,7 @@
(and (symbol? u-prop)
(or (known-struct-type-property/immediate-guard?
(find-known u-prop prim-knowns knowns imports mutated)))
(simple? val prim-knowns knowns imports mutated))))]
(simple? val prim-knowns knowns imports mutated simples))))]
[`null #t]
[`'() #t]
[`,_ #f]))