;;; cpvalid.ss ;;; Copyright 1984-2017 Cisco Systems, Inc. ;;; ;;; Licensed under the Apache License, Version 2.0 (the "License"); ;;; you may not use this file except in compliance with the License. ;;; You may obtain a copy of the License at ;;; ;;; http://www.apache.org/licenses/LICENSE-2.0 ;;; ;;; Unless required by applicable law or agreed to in writing, software ;;; distributed under the License is distributed on an "AS IS" BASIS, ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ;;; See the License for the specific language governing permissions and ;;; limitations under the License. ;;; see comments relating to both cpvalid and cpletrec at front of ;;; cpletrec.ss (begin (define undefined-variable-warnings ($make-thread-parameter #f (lambda (x) (and x #t)))) (let () (import (nanopass)) (include "base-lang.ss") (define-pass cpvalid : Lsrc (x) -> Lsrc () (definitions (with-output-language (Lsrc Expr) (define build-let (lambda (ids vals body) (if (null? ids) body `(call ,(make-preinfo) (case-lambda ,(make-preinfo-lambda) (clause (,ids ...) ,(length ids) ,body)) ,vals ...)))) (define build-letrec (lambda (ids vals body) (if (null? ids) ; dropping source here; could attach to body or add source record body `(letrec ([,ids ,vals] ...) ,body)))) (define build-letrec* (lambda (ids vals body) (if (null? ids) ; dropping source here; could attach to body or add source record body `(letrec* ([,ids ,vals] ...) ,body))))) (define-record-type proxy (fields (mutable state)) (nongenerative) (sealed #t) (protocol (lambda (new) (lambda () (new 'protectable))))) (define-syntax with-protected (syntax-rules () [(_ p e) (identifier? #'p) (begin (when p (proxy-state-set! p 'protected)) (let-values ([t (let () (define-syntax p (lambda (x) (syntax-error x "can't reference proxy inside with-protected"))) e)]) (when p (proxy-state-set! p 'protectable)) (apply values t)))])) (define-syntax with-unprotected (syntax-rules () [(_ p e) (identifier? #'p) (begin (when p (proxy-state-set! p 'unprotected)) (let-values ([t (let () (define-syntax p (lambda (x) (syntax-error x "can't reference proxy inside with-unprotected"))) e)]) (when p (proxy-state-set! p 'protectable)) (apply values t)))])) (module (with-info with-valid* with-valid** with-proxy with-proxy* prelex-info-proxy prelex-info-valid-flag set-prelex-info-unsafe! prelex-info-unsafe set-prelex-info-referenced! prelex-info-referenced) (define-record-type info (fields (mutable proxy) (mutable unsafe) (mutable valid-flag) (mutable referenced)) (nongenerative) (sealed #t) (protocol (lambda (new) (lambda () (new #f #f #f #f))))) (define-syntax with-info (syntax-rules () [(_ ids-expr e) (let ([ids ids-expr]) (for-each (lambda (id) (safe-assert (not (prelex-operand id))) (prelex-operand-set! id (make-info))) ids) (let-values ([t e]) (for-each (lambda (id) (safe-assert (prelex-operand id)) (prelex-operand-set! id #f)) ids) (apply values t)))])) (define set-prelex-info-valid-flag! (lambda (id val) (info-valid-flag-set! (prelex-operand id) val))) (define prelex-info-valid-flag (lambda (id) (let ([info (prelex-operand id)]) (and info (info-valid-flag info))))) (define-syntax with-valid* (syntax-rules () [(_ valid-flag-expr ids-expr e) (let ([valid-flag valid-flag-expr] [ids ids-expr]) (for-each (lambda (id) (set-prelex-info-valid-flag! id valid-flag)) ids) (let-values ([t e]) (for-each (lambda (id) (set-prelex-info-valid-flag! id #f)) ids) (apply values t)))])) (define-syntax with-valid** (syntax-rules () [(_ valid-flags-expr ids-expr e) (let ([valid-flags valid-flags-expr] [ids ids-expr]) (for-each (lambda (id vf) (set-prelex-info-valid-flag! id vf)) ids valid-flags) (let-values ([t e]) (for-each (lambda (id) (set-prelex-info-valid-flag! id #f)) ids) (apply values t)))])) (define-who set-prelex-info-proxy! (lambda (id val) (let ([info (prelex-operand id)]) (safe-assert info) (info-proxy-set! info val)))) (define prelex-info-proxy (lambda (id) (let ([info (prelex-operand id)]) (and info (info-proxy info))))) (define-syntax with-proxy (syntax-rules () [(_ proxy-expr id-expr e) (let ([proxy proxy-expr] [id id-expr]) (set-prelex-info-proxy! id proxy) (let ([t e]) (set-prelex-info-proxy! id #f) t))])) (define-syntax with-proxy* (syntax-rules () [(_ proxy-expr ids-expr e) (let ([proxy proxy-expr] [ids ids-expr]) (for-each (lambda (id) (set-prelex-info-proxy! id proxy)) ids) (let-values ([t e]) (for-each (lambda (id) (set-prelex-info-proxy! id #f)) ids) (apply values t)))])) (define set-prelex-info-unsafe! (lambda (id val) (info-unsafe-set! (prelex-operand id) val))) (define prelex-info-unsafe (lambda (id) (info-unsafe (prelex-operand id)))) (define set-prelex-info-referenced! (lambda (id val) (let ([info (prelex-operand id)]) (when info (info-referenced-set! info val))))) (define prelex-info-referenced (lambda (id) (info-referenced (prelex-operand id))))) (with-output-language (Lsrc Expr) (define insert-valid-check (lambda (what maybe-src id p x) (if (and p (not (eq? (proxy-state p) 'protected))) (let ([valid-flag (prelex-info-valid-flag id)]) (if valid-flag (let ([name (prelex-name id)]) (let ([mesg (format "attempt to ~a undefined variable ~~s" what)]) (when (undefined-variable-warnings) ($source-warning #f maybe-src #t (format "possible ~a" mesg) name)) (if (prelex-referenced valid-flag) (set-prelex-multiply-referenced! valid-flag #t) (set-prelex-referenced! valid-flag #t)) `(seq (if (ref #f ,valid-flag) (quote ,(void)) (call ,(make-preinfo) ,(lookup-primref 2 '$source-violation) (quote #f) (quote ,maybe-src) (quote #t) (quote ,mesg) (quote ,name))) ,x))) x)) x)))) ; wl = worklist ; dl = deferred list (define (process-letrec-bindings cpvalid proxy proxy-ids ids vals unsafe* dl?) (let f ([wl (map list ids vals unsafe*)] [dl '()] [oops #f]) (if (null? wl) (if oops (f dl '() #f) (with-proxy* proxy proxy-ids (map/ormap (lambda (x) (apply (lambda (id val unsafe) (let-values ([(val dl?) (cpvalid val proxy dl?)]) (values (cons id val) dl?))) x)) dl))) (apply (lambda (id val unsafe) (define update (lambda (x) (apply (lambda (id val unsafe) (if (or unsafe (prelex-info-referenced id)) (begin (set-prelex-info-referenced! id #f) (list id val #t)) x)) x))) (if unsafe (let ([val (with-unprotected proxy (let ([proxy (make-proxy)]) (with-proxy* proxy proxy-ids (first-value (cpvalid val proxy #f)))))]) (let-values ([(ls dl?) (f (map update (cdr wl)) (map update dl) #t)]) (values (cons (cons id val) ls) dl?))) (f (cdr wl) (cons (car wl) dl) oops))) (car wl))))) (define map/ormap (case-lambda [(p ls) (if (null? ls) (values '() #f) (let-values ([(x b1) (p (car ls))] [(ls b2) (map/ormap p (cdr ls))]) (values (cons x ls) (or b1 b2))))] [(p ls1 ls2) (if (null? ls1) (values '() #f) (let-values ([(x b1) (p (car ls1) (car ls2))] [(ls b2) (map/ormap p (cdr ls1) (cdr ls2))]) (values (cons x ls) (or b1 b2))))])) (define deferred? (lambda (x) (nanopass-case (Lsrc Expr) x [(cpvalid-defer ,e) #t] [else #f]))) (with-output-language (Lsrc Expr) (define defer-or-not (lambda (dl? x) (values (if (and dl? (not (deferred? x))) `(cpvalid-defer ,x) x) dl?)))) (define-syntax first-value (syntax-rules () [(_ e) (let-values ([(x . r) e]) x)])) (define undefer* (lambda (ls proxy dl?) (map/ormap (lambda (x) (undefer x proxy dl?)) ls)))) (undefer : Expr (x proxy dl?) -> Expr (dl?) [(cpvalid-defer ,[undefer-helper : e dl?]) (values e dl?)] [else (values x #f)]) (undefer-helper : Expr (x proxy dl?) -> Expr (dl?) [(ref ,maybe-src ,x) (values x #f)] [(quote ,d) (values x #f)] [,pr (values x #f)] ; recognize canonical form of a let after expansion [(call ,preinfo0 (case-lambda ,preinfo1 (clause (,x* ...) ,interface ,[undefer : body body-dl?])) ,e* ...) (guard (fx= (length e*) interface)) (let-values ([(e* args-dl?) (undefer* e* proxy dl?)]) (defer-or-not (or body-dl? args-dl?) `(call ,preinfo0 (case-lambda ,preinfo1 (clause (,x* ...) ,interface ,body)) ,e* ...)))] [(call ,preinfo ,[undefer : e fun-dl?] ,e* ...) (let-values ([(e* args-dl?) (undefer* e* proxy dl?)]) (defer-or-not (or fun-dl? args-dl?) `(call ,preinfo ,e ,e* ...)))] [(if ,[undefer : e0 dl0?] ,[undefer : e1 dl1?] ,[undefer : e2 dl2?]) (defer-or-not (or dl0? dl1? dl2?) `(if ,e0 ,e1 ,e2))] [(case-lambda ,preinfo ,cl* ...) (cpvalid `(case-lambda ,preinfo ,cl* ...) proxy dl?)] [(seq ,[undefer : e1 dl1?] ,[undefer : e2 dl2?]) (defer-or-not (or dl1? dl2?) `(seq ,e1 ,e2))] [(set! ,maybe-src ,x ,[undefer : e dl?]) (defer-or-not dl? `(set! ,maybe-src ,x ,e))] [(letrec ([,x* ,e*] ...) ,[undefer : body body-dl?]) (let-values ([(e* vals-dl?) (undefer* e* proxy dl?)]) (defer-or-not (or body-dl? vals-dl?) `(letrec ([,x* ,e*] ...) ,body)))] [(letrec* ([,x* ,e*] ...) ,[undefer : body body-dl?]) (let-values ([(e* vals-dl?) (undefer* e* proxy dl?)]) (defer-or-not (or body-dl? vals-dl?) `(letrec* ([,x* ,e*] ...) ,body)))] [(foreign (,conv* ...) ,name ,[undefer : e dl?] (,arg-type* ...) ,result-type) (defer-or-not dl? `(foreign (,conv* ...) ,name ,e (,arg-type* ...) ,result-type))] [(fcallable (,conv* ...) ,[undefer : e dl?] (,arg-type* ...) ,result-type) (defer-or-not dl? `(fcallable (,conv* ...) ,e (,arg-type* ...) ,result-type))] [(cte-optimization-loc ,box ,[undefer : e dl?]) (defer-or-not dl? `(cte-optimization-loc ,box ,e))] [(pariah) (values x #f)] [(profile ,src) (values x #f)] [(moi) (values x #f)] [else (sorry! who "unexpected record ~s" x)]) (CaseLambdaClause : CaseLambdaClause (ir proxy) -> CaseLambdaClause () [(clause (,x* ...) ,interface ,body) (let-values ([(body dl?) (with-protected proxy (cpvalid body #f #f))]) `(clause (,x* ...) ,interface ,body))]) (cpvalid : Expr (x proxy dl?) -> Expr (dl?) [(ref ,maybe-src ,x) (set-prelex-info-referenced! x #t) (values (let ([p (prelex-info-proxy x)]) ; unsafe => x might be called. this can only happen if x has ; gotten into the unprotected state (when (and p (eq? (proxy-state p) 'unprotected)) (set-prelex-info-unsafe! x #t)) (insert-valid-check "reference" maybe-src x p `(ref ,maybe-src ,x))) #f)] [,pr (values x #f)] [(quote ,d) (values x #f)] [(call ,preinfo ,pr ,e* ...) (guard (all-set? (prim-mask (or proc discard)) (primref-flags pr))) (let-values ([(e* dl?) (map/ormap (lambda (e) (cpvalid e proxy dl?)) e*)]) (defer-or-not dl? `(call ,preinfo ,pr ,e* ...)))] ; recognize canonical form of a let after expansion [(call ,preinfo0 (case-lambda ,preinfo1 (clause (,x* ...) ,interface ,body)) ,e* ...) (guard (fx= (length e*) interface)) (let ([proxy (or proxy (make-proxy))]) (with-info x* (with-proxy* proxy x* (let-values ([(body body-dl?) (cpvalid body proxy dl?)]) (let-values ([(e* dl?) (map/ormap (lambda (arg id) (if (prelex-info-unsafe id) (with-unprotected proxy (cpvalid arg #f #f)) (cpvalid arg proxy dl?))) e* x*)]) (defer-or-not (or dl? body-dl?) `(call ,preinfo0 (case-lambda ,preinfo1 (clause (,x* ...) ,interface ,body)) ,e* ...)))))))] [(call ,preinfo ,e ,e* ...) (values (with-unprotected proxy `(call ,preinfo ,(first-value (cpvalid e #f #f)) ,(map (lambda (x) (first-value (cpvalid x #f #f))) e*) ...)) #f)] [(case-lambda ,preinfo ,cl* ...) (if dl? (values `(cpvalid-defer ,x) #t) (values `(case-lambda ,preinfo ,(map (lambda (cl) (CaseLambdaClause cl proxy)) cl*) ...) #f))] [(set! ,maybe-src ,x ,e) (let-values ([(e dl?) ; rhs is unsafe only if x is referenced (if (prelex-referenced x) (with-unprotected proxy (cpvalid e #f #f)) (cpvalid e proxy dl?))]) (defer-or-not dl? (insert-valid-check "assign" maybe-src x (prelex-info-proxy x) (first-value (defer-or-not dl? `(set! ,maybe-src ,x ,e))))))] [(letrec ([,x* ,e*] ...) ,body) (with-info x* (let*-values ([(proxy) (or proxy (make-proxy))] [(valid-flag) (make-prelex* 'valid?)] [(body body-dl?) (with-proxy* proxy x* (cpvalid body proxy dl?))] [(unsafe*) (map prelex-info-unsafe x*)]) (for-each (lambda (id) (set-prelex-info-unsafe! id #f) (set-prelex-info-referenced! id #f)) x*) (let*-values ([(alist dl?) (with-valid* valid-flag x* (process-letrec-bindings cpvalid proxy x* x* e* unsafe* dl?))] [(e*) (map (lambda (id) (cdr (assq id alist))) x*)]) (defer-or-not (or dl? body-dl?) (if (prelex-referenced valid-flag) (begin (set-prelex-assigned! valid-flag #t) (build-let (list valid-flag) (list `(quote #f)) (first-value (let-values ([(body body-dl?) (defer-or-not body-dl? `(seq (set! #f ,valid-flag (quote #t)) ,body))]) (defer-or-not (or dl? body-dl?) (build-letrec x* e* body)))))) (build-letrec x* e* body))))))] [(letrec* ([,x* ,e*] ...) ,body) ; - we do unprotected parts of each rhs plus unsafe lambda pieces ; first and leave remaining lambda expressions to do later. ; - a full-blown flow analysis could be even nicer and even make it ; possible to detect references and assignments that are surely ; bad. (with-info x* (let*-values ([(proxy) (or proxy (make-proxy))] [(valid-flags) (map (lambda (id) (make-prelex* 'valid?)) x*)] [(body body-dl?) (with-proxy* proxy x* (cpvalid body proxy dl?))] [(unsafe*) (map prelex-info-unsafe x*)]) (define-record-type welt (nongenerative) (sealed #t) (fields id (mutable val) unsafe (mutable forbidden-ids) (mutable valid-flags))) (define (make-welts x* e* unsafe* valid-flags) (let f ([x* x*] [e* e*] [unsafe* unsafe*] [valid-flags valid-flags]) (if (null? x*) '() (cons (make-welt (car x*) (car e*) (car unsafe*) x* valid-flags) (f (cdr x*) (cdr e*) (cdr unsafe*) (cdr valid-flags)))))) (define (process-ws w* d*) (if (null? w*) (process-letrec-bindings undefer proxy '() (map welt-id d*) (map welt-val d*) (map welt-unsafe d*) dl?) (let ([w (car w*)]) (let ([id (welt-id w)] [val (welt-val w)] [unsafe (welt-unsafe w)] [forbidden-ids (welt-forbidden-ids w)] [valid-flags (welt-valid-flags w)]) (if (prelex-info-referenced id) (let ([val (with-proxy* proxy forbidden-ids (with-unprotected proxy (with-valid** valid-flags forbidden-ids (first-value ; could obviate this test with ; cpvalid-defer case in cpvalid (if (deferred? val) (undefer val #f #f) (cpvalid val #f #f))))))]) (let-values ([(ls dl?) (process-ds (cdr w*) d* id (car valid-flags))]) (values (cons (cons id val) ls) dl?))) (let-values ([(val dl?) (with-proxy* proxy forbidden-ids (with-unprotected proxy (with-valid** valid-flags forbidden-ids (cpvalid val #f #t))))]) (if dl? (begin ; deferred parts of rhs can reference own lhs, so remove it from forbidden list (welt-val-set! w val) (welt-forbidden-ids-set! w (cdr forbidden-ids)) (welt-valid-flags-set! w (cdr valid-flags)) (process-ds (cdr w*) (cons w d*) id (car valid-flags))) (let-values ([(ls dl?) (process-ds (cdr w*) d* id (car valid-flags))]) (values (cons (cons id val) ls) dl?))))))))) (define (process-ds w* d* okay-before-id okay-before-valid-flags) ; it's okay to reference any rhs before okay-before-id ; trim forbidden lists accordingly (for-each (lambda (w) (cond [(memq okay-before-id (welt-forbidden-ids w)) => (lambda (x*) (welt-forbidden-ids-set! w x*) (welt-valid-flags-set! w (memq okay-before-valid-flags (welt-valid-flags w))))])) d*) (let f ([d* d*] [new-d* '()] [oops? #f]) (if (null? d*) (if oops? (f new-d* '() #f) (process-ws w* new-d*)) (let* ([w (car d*)] [id (welt-id w)]) (if (prelex-info-referenced id) (let ([val (with-proxy* proxy (welt-forbidden-ids w) (with-unprotected proxy (with-valid** (welt-valid-flags w) (welt-forbidden-ids w) (first-value (undefer (welt-val w) #f #f)))))]) (let-values ([(ls dl?) (f (cdr d*) new-d* #t)]) (values (cons (cons id val) ls) dl?))) (f (cdr d*) (cons w new-d*) oops?)))))) (for-each (lambda (id) (set-prelex-info-unsafe! id #f) (set-prelex-info-referenced! id #f)) x*) (let*-values ([(alist dl?) (process-ws (make-welts x* e* unsafe* valid-flags) '())] [(e*) (map (lambda (id) (cdr (assq id alist))) x*)] [(x* e* valid-flags) (let f ([x* x*] [e* e*] [valid-flags valid-flags]) (if (null? x*) (values '() '() '()) (let ([id (car x*)] [val (car e*)] [vf (car valid-flags)]) (let-values ([(x* e* valid-flags) (f (cdr x*) (cdr e*) (cdr valid-flags))]) (if (prelex-referenced vf) (begin (set-prelex-assigned! vf #t) (values (list* id (make-prelex* 'dummy) x*) (list* val `(set! #f ,vf (quote #t)) e*) (cons vf valid-flags))) (values (cons id x*) (cons val e*) valid-flags))))))]) (defer-or-not (or dl? body-dl?) (build-let valid-flags (make-list (length valid-flags) `(quote #f)) (first-value (defer-or-not (or dl? body-dl?) (build-letrec* x* e* body))))))))] [(if ,[cpvalid : e0 dl0?] ,[cpvalid : e1 dl1?] ,[cpvalid : e2 dl2?]) (defer-or-not (or dl0? dl1? dl2?) `(if ,e0 ,e1 ,e2))] [(seq ,[cpvalid : e1 dl1?] ,[cpvalid : e2 dl2?]) (defer-or-not (or dl1? dl2?) `(seq ,e1 ,e2))] [(foreign (,conv* ...) ,name ,[cpvalid : e dl?] (,arg-type* ...) ,result-type) (defer-or-not dl? `(foreign (,conv* ...) ,name ,e (,arg-type* ...) ,result-type))] [(fcallable (,conv* ...) ,[cpvalid : e dl?] (,arg-type* ...) ,result-type) (defer-or-not dl? `(fcallable (,conv* ...) ,e (,arg-type* ...) ,result-type))] [(cte-optimization-loc ,box ,[cpvalid : e dl?]) (defer-or-not dl? `(cte-optimization-loc ,box ,e))] [(pariah) (values x #f)] [(profile ,src) (values x #f)] [(moi) (values x #f)] [else (sorry! who "unexpected record ~s" x)]) (first-value (cpvalid x #f #f))) (set! $cpvalid (lambda (x) (if (= (optimize-level) 3) x (cpvalid x))))) )