diff --git a/racket/src/cs/Makefile b/racket/src/cs/Makefile index 685d3b48a6..400d7d7d1e 100644 --- a/racket/src/cs/Makefile +++ b/racket/src/cs/Makefile @@ -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 \ diff --git a/racket/src/cs/convert.rkt b/racket/src/cs/convert.rkt index be749b51bc..ae4f1f6a1b 100644 --- a/racket/src/cs/convert.rkt +++ b/racket/src/cs/convert.rkt @@ -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))) diff --git a/racket/src/cs/linklet/annotation.ss b/racket/src/cs/linklet/annotation.ss index 740302bd38..becfccd64c 100644 --- a/racket/src/cs/linklet/annotation.ss +++ b/racket/src/cs/linklet/annotation.ss @@ -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))]) diff --git a/racket/src/cs/rumble.sls b/racket/src/cs/rumble.sls index feebaf0b9a..ae74f17478 100644 --- a/racket/src/cs/rumble.sls +++ b/racket/src/cs/rumble.sls @@ -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") diff --git a/racket/src/cs/rumble/letrec.ss b/racket/src/cs/rumble/letrec.ss deleted file mode 100644 index a83b8c7961..0000000000 --- a/racket/src/cs/rumble/letrec.ss +++ /dev/null @@ -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 ...))))])])) diff --git a/racket/src/schemify/find-definition.rkt b/racket/src/schemify/find-definition.rkt index 88dc3c4169..698b15cd85 100644 --- a/racket/src/schemify/find-definition.rkt +++ b/racket/src/schemify/find-definition.rkt @@ -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)] diff --git a/racket/src/schemify/infer-known.rkt b/racket/src/schemify/infer-known.rkt index 6495d3b386..6fba40d6ad 100644 --- a/racket/src/schemify/infer-known.rkt +++ b/racket/src/schemify/infer-known.rkt @@ -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])) diff --git a/racket/src/schemify/left-to-right.rkt b/racket/src/schemify/left-to-right.rkt index 12b5ea53eb..78bfbd4168 100644 --- a/racket/src/schemify/left-to-right.rkt +++ b/racket/src/schemify/left-to-right.rkt @@ -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]) diff --git a/racket/src/schemify/letrec.rkt b/racket/src/schemify/letrec.rkt index 9871822592..b490ecfd7c 100644 --- a/racket/src/schemify/letrec.rkt +++ b/racket/src/schemify/letrec.rkt @@ -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)) diff --git a/racket/src/schemify/main.rkt b/racket/src/schemify/main.rkt index a5af87ac29..f0856a9cc9 100644 --- a/racket/src/schemify/main.rkt +++ b/racket/src/schemify/main.rkt @@ -12,7 +12,7 @@ schemify-body (all-from-out "known.rkt") - + lift-in-schemified-linklet lift-in-schemified-body diff --git a/racket/src/schemify/mutated-state.rkt b/racket/src/schemify/mutated-state.rkt index d86913f0a0..ba87da3cdb 100644 --- a/racket/src/schemify/mutated-state.rkt +++ b/racket/src/schemify/mutated-state.rkt @@ -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])) diff --git a/racket/src/schemify/mutated.rkt b/racket/src/schemify/mutated.rkt index 61f60f44b4..83ab49690b 100644 --- a/racket/src/schemify/mutated.rkt +++ b/racket/src/schemify/mutated.rkt @@ -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])] diff --git a/racket/src/schemify/schemify.rkt b/racket/src/schemify/schemify.rkt index ef7f0511dd..76e7d63ec4 100644 --- a/racket/src/schemify/schemify.rkt +++ b/racket/src/schemify/schemify.rkt @@ -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) diff --git a/racket/src/schemify/simple.rkt b/racket/src/schemify/simple.rkt index 7e4129ee0b..d1f553e89a 100644 --- a/racket/src/schemify/simple.rkt +++ b/racket/src/schemify/simple.rkt @@ -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) diff --git a/racket/src/schemify/struct-convert.rkt b/racket/src/schemify/struct-convert.rkt index 4ba313cd79..e9aeb7e0cc 100644 --- a/racket/src/schemify/struct-convert.rkt +++ b/racket/src/schemify/struct-convert.rkt @@ -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? diff --git a/racket/src/schemify/struct-type-info.rkt b/racket/src/schemify/struct-type-info.rkt index e6b9fb4b43..85161865b5 100644 --- a/racket/src/schemify/struct-type-info.rkt +++ b/racket/src/schemify/struct-type-info.rkt @@ -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]))