diff --git a/module/Makefile.am b/module/Makefile.am index 7af35ed8f..1b84f1d02 100644 --- a/module/Makefile.am +++ b/module/Makefile.am @@ -138,6 +138,8 @@ CPS_LANG_SOURCES = \ language/cps/simplify.scm \ language/cps/spec.scm \ language/cps/specialize-primcalls.scm \ + language/cps/types.scm \ + language/cps/type-fold.scm \ language/cps/verify.scm BYTECODE_LANG_SOURCES = \ diff --git a/module/language/cps/compile-bytecode.scm b/module/language/cps/compile-bytecode.scm index e958b4cf8..8c0fa6fe1 100644 --- a/module/language/cps/compile-bytecode.scm +++ b/module/language/cps/compile-bytecode.scm @@ -44,6 +44,7 @@ #:use-module (language cps simplify) #:use-module (language cps slot-allocation) #:use-module (language cps specialize-primcalls) + #:use-module (language cps type-fold) #:use-module (system vm assembler) #:export (compile-bytecode)) @@ -73,6 +74,7 @@ (exp (run-pass exp elide-values #:elide-values? #t)) (exp (run-pass exp prune-bailouts #:prune-bailouts? #t)) (exp (run-pass exp eliminate-common-subexpressions #:cse? #t)) + (exp (run-pass exp type-fold #:type-fold? #f)) (exp (run-pass exp resolve-self-references #:resolve-self-references? #t)) (exp (run-pass exp eliminate-dead-code #:eliminate-dead-code? #t)) (exp (run-pass exp simplify #:simplify? #t))) diff --git a/module/language/cps/type-fold.scm b/module/language/cps/type-fold.scm new file mode 100644 index 000000000..ca02fec93 --- /dev/null +++ b/module/language/cps/type-fold.scm @@ -0,0 +1,266 @@ +;;; Abstract constant folding on CPS +;;; Copyright (C) 2014 Free Software Foundation, Inc. +;;; +;;; This library is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU Lesser General Public License as +;;; published by the Free Software Foundation, either version 3 of the +;;; License, or (at your option) any later version. +;;; +;;; This library is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;;; Lesser General Public License for more details. +;;; +;;; You should have received a copy of the GNU Lesser General Public +;;; License along with this program. If not, see +;;; . + +;;; Commentary: +;;; +;;; This pass uses the abstract interpretation provided by type analysis +;;; to fold constant values and type predicates. It is most profitably +;;; run after CSE, to take advantage of scalar replacement. +;;; +;;; Code: + +(define-module (language cps type-fold) + #:use-module (ice-9 match) + #:use-module (language cps) + #:use-module (language cps dfg) + #:use-module (language cps renumber) + #:use-module (language cps types) + #:export (type-fold)) + +(define &scalar-types + (logior &exact-integer &flonum &char &unspecified &boolean &nil &null)) + +(define *branch-folders* (make-hash-table)) + +(define-syntax-rule (define-branch-folder name f) + (hashq-set! *branch-folders* 'name f)) + +(define-syntax-rule (define-branch-folder-alias to from) + (hashq-set! *branch-folders* 'to (hashq-ref *branch-folders* 'from))) + +(define-syntax-rule (define-unary-branch-folder (name arg min max) body ...) + (define-branch-folder name (lambda (arg min max) body ...))) + +(define-syntax-rule (define-binary-branch-folder (name arg0 min0 max0 + arg1 min1 max1) + body ...) + (define-branch-folder name (lambda (arg0 min0 max0 arg1 min1 max1) body ...))) + +(define-syntax-rule (define-unary-type-predicate-folder name &type) + (define-unary-branch-folder (name type min max) + (let ((type* (logand type &type))) + (cond + ((zero? type*) (values #t #f)) + ((eqv? type type*) (values #t #t)) + (else (values #f #f)))))) + +;; All the cases that are in compile-bytecode. +(define-unary-type-predicate-folder pair? &pair) +(define-unary-type-predicate-folder null? &null) +(define-unary-type-predicate-folder nil? &nil) +(define-unary-type-predicate-folder symbol? &symbol) +(define-unary-type-predicate-folder variable? &box) +(define-unary-type-predicate-folder vector? &vector) +(define-unary-type-predicate-folder struct? &struct) +(define-unary-type-predicate-folder string? &string) +(define-unary-type-predicate-folder number? &number) +(define-unary-type-predicate-folder char? &char) + +(define-binary-branch-folder (eq? type0 min0 max0 type1 min1 max1) + (cond + ((or (zero? (logand type0 type1)) (< max0 min1) (< max1 min0)) + (values #t #f)) + ((and (eqv? type0 type1) + (eqv? min0 min1 max0 max1) + (zero? (logand type0 (1- type0))) + (not (zero? (logand type0 &scalar-types)))) + (values #t #t)) + (else + (values #f #f)))) +(define-branch-folder-alias eqv? eq?) +(define-branch-folder-alias equal? eq?) + +(define (compare-ranges type0 min0 max0 type1 min1 max1) + (and (zero? (logand (logior type0 type1) (lognot &real))) + (cond ((< max0 min1) '<) + ((> min0 max1) '>) + ((= min0 max0 min1 max1) '=) + ((<= max0 min1) '<=) + ((>= min0 max1) '>=) + (else #f)))) + +(define-binary-branch-folder (< type0 min0 max0 type1 min1 max1) + (case (compare-ranges type0 min0 max0 type1 min1 max1) + ((<) (values #t #t)) + ((= >= >) (values #t #f)) + (else (values #f #f)))) + +(define-binary-branch-folder (<= type0 min0 max0 type1 min1 max1) + (case (compare-ranges type0 min0 max0 type1 min1 max1) + ((< <= =) (values #t #t)) + ((>) (values #t #f)) + (else (values #f #f)))) + +(define-binary-branch-folder (= type0 min0 max0 type1 min1 max1) + (case (compare-ranges type0 min0 max0 type1 min1 max1) + ((=) (values #t #t)) + ((< >) (values #t #f)) + (else (values #f #f)))) + +(define-binary-branch-folder (>= type0 min0 max0 type1 min1 max1) + (case (compare-ranges type0 min0 max0 type1 min1 max1) + ((> >= =) (values #t #t)) + ((<) (values #t #f)) + (else (values #f #f)))) + +(define-binary-branch-folder (> type0 min0 max0 type1 min1 max1) + (case (compare-ranges type0 min0 max0 type1 min1 max1) + ((>) (values #t #t)) + ((= <= <) (values #t #f)) + (else (values #f #f)))) + +(define (compute-folded fun dfg min-label min-var) + (define (scalar-value type val) + (cond + ((eqv? type &exact-integer) val) + ((eqv? type &flonum) (exact->inexact val)) + ((eqv? type &char) (integer->char val)) + ((eqv? type &unspecified) *unspecified*) + ((eqv? type &boolean) (not (zero? val))) + ((eqv? type &nil) #nil) + ((eqv? type &null) '()) + (else (error "unhandled type" type val)))) + (let* ((typev (infer-types fun dfg)) + (folded? (make-bitvector (/ (vector-length typev) 2) #f)) + (folded-values (make-vector (bitvector-length folded?) #f))) + (define (label->idx label) (- label min-label)) + (define (var->idx var) (- var min-var)) + (define (maybe-fold-value! label name k def) + (call-with-values (lambda () (lookup-post-type typev label def)) + (lambda (type min max) + (when (and (not (zero? type)) + (zero? (logand type (1- type))) + (zero? (logand type (lognot &scalar-types))) + (eqv? min max)) + (bitvector-set! folded? label #t) + (vector-set! folded-values label (scalar-value type min)))))) + (define (maybe-fold-unary-branch! label name arg) + (let* ((folder (hashq-ref *branch-folders* name))) + (when folder + (call-with-values (lambda () (lookup-pre-type typev label arg)) + (lambda (type min max) + (call-with-values (lambda () (folder type min max)) + (lambda (f? v) + (bitvector-set! folded? label f?) + (vector-set! folded-values label v)))))))) + (define (maybe-fold-binary-branch! label name arg0 arg1) + (let* ((folder (hashq-ref *branch-folders* name))) + (when folder + (call-with-values (lambda () (lookup-pre-type typev label arg0)) + (lambda (type0 min0 max0) + (call-with-values (lambda () (lookup-pre-type typev label arg1)) + (lambda (type1 min1 max1) + (call-with-values (lambda () + (folder type0 min0 max0 type1 min1 max1)) + (lambda (f? v) + (bitvector-set! folded? label f?) + (vector-set! folded-values label v)))))))))) + (define (visit-cont cont) + (match cont + (($ $cont label ($ $kargs _ _ body)) + (visit-term body label)) + (($ $cont label ($ $kclause arity body alternate)) + (visit-cont body) + (visit-cont alternate)) + (_ #f))) + (define (visit-term term label) + (match term + (($ $letk conts body) + (for-each visit-cont conts) + (visit-term body label)) + (($ $letrec _ _ _ body) + (visit-term body label)) + (($ $continue k src ($ $primcall name args)) + ;; We might be able to fold primcalls that define a value or + ;; that branch. + (match (lookup-cont k dfg) + (($ $kargs (_) (def)) + (maybe-fold-value! (label->idx label) name (label->idx k) + (var->idx def))) + (($ $kif kt kf) + (match args + ((arg) + (maybe-fold-unary-branch! (label->idx label) name + (var->idx arg))) + ((arg0 arg1) + (maybe-fold-binary-branch! (label->idx label) name + (var->idx arg0) (var->idx arg1))))) + (_ #f))) + (_ #f))) + (match fun + (($ $cont kfun ($ $kfun src meta self tail clause)) + (visit-cont clause))) + (values folded? folded-values))) + +(define (fold-constants* fun dfg) + (match fun + (($ $cont min-label ($ $kfun _ _ min-var)) + (call-with-values (lambda () (compute-folded fun dfg min-label min-var)) + (lambda (folded? folded-values) + (define (label->idx label) (- label min-label)) + (define (var->idx var) (- var min-var)) + (define (visit-cont cont) + (rewrite-cps-cont cont + (($ $cont label ($ $kargs names syms body)) + (label ($kargs names syms ,(visit-term body label)))) + (($ $cont label ($ $kclause arity body alternate)) + (label ($kclause ,arity ,(visit-cont body) + ,(and alternate (visit-cont alternate))))) + (_ ,cont))) + (define (visit-term term label) + (rewrite-cps-term term + (($ $letk conts body) + ($letk ,(map visit-cont conts) + ,(visit-term body label))) + (($ $letrec names vars funs body) + ($letrec names vars (map visit-fun funs) + ,(visit-term body label))) + (($ $continue k src (and fun ($ $fun))) + ($continue k src ,(visit-fun fun))) + (($ $continue k src (and primcall ($ $primcall))) + ,(if (bitvector-ref folded? (label->idx label)) + (let ((val (vector-ref folded-values (label->idx label)))) + ;; Uncomment for debugging. + ;; (pk 'folded src primcall val) + (match (lookup-cont k dfg) + (($ $kargs) + (let-fresh (k*) (v*) + ;; Rely on DCE to elide this expression, if + ;; possible. + (build-cps-term + ($letk ((k* ($kargs (#f) (v*) + ($continue k src ($const val))))) + ($continue k* src ,primcall))))) + (($ $kif kt kf) + ;; Folded branch. + (build-cps-term + ($continue (if val kt kf) src ($values ())))))) + term)) + (_ ,term))) + (define (visit-fun fun) + (rewrite-cps-exp fun + (($ $fun free body) + ($fun free ,(fold-constants* body dfg))))) + (rewrite-cps-cont fun + (($ $cont kfun ($ $kfun src meta self tail clause)) + (kfun ($kfun src meta self ,tail ,(visit-cont clause)))))))))) + +(define (type-fold fun) + (let* ((fun (renumber fun)) + (dfg (compute-dfg fun))) + (with-fresh-name-state-from-dfg dfg + (fold-constants* fun dfg)))) diff --git a/module/language/cps/types.scm b/module/language/cps/types.scm new file mode 100644 index 000000000..44deb0467 --- /dev/null +++ b/module/language/cps/types.scm @@ -0,0 +1,1436 @@ +;;; Type analysis on CPS +;;; Copyright (C) 2014 Free Software Foundation, Inc. +;;; +;;; This library is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU Lesser General Public License as +;;; published by the Free Software Foundation, either version 3 of the +;;; License, or (at your option) any later version. +;;; +;;; This library is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;;; Lesser General Public License for more details. +;;; +;;; You should have received a copy of the GNU Lesser General Public +;;; License along with this program. If not, see +;;; . + +;;; Commentary: +;;; +;;; Type analysis computes the possible types and ranges that values may +;;; have at all program positions. This analysis can help to prove that +;;; a primcall has no side-effects, if its arguments have the +;;; appropriate type and range. It can also enable constant folding of +;;; type predicates and, in the future, enable the compiler to choose +;;; untagged, unboxed representations for numbers. +;;; +;;; For the purposes of this analysis, a "type" is an aspect of a value +;;; that will not change. Guile's CPS intermediate language does not +;;; carry manifest type information that asserts properties about given +;;; values; instead, we recover this information via flow analysis, +;;; garnering properties from type predicates, constant literals, +;;; primcall results, and primcalls that assert that their arguments are +;;; of particular types. +;;; +;;; A range denotes a subset of the set of values in a type, bounded by +;;; a minimum and a maximum. The precise meaning of a range depends on +;;; the type. For real numbers, the range indicates an inclusive lower +;;; and upper bound on the integer value of a type. For vectors, the +;;; range indicates the length of the vector. The range is limited to a +;;; signed 32-bit value, with the smallest and largest values indicating +;;; -inf.0 and +inf.0, respectively. For some types, like pairs, the +;;; concept of "range" makes no sense. In these cases we consider the +;;; range to be -inf.0 to +inf.0. +;;; +;;; Types are represented as a bitfield. Fewer bits means a more precise +;;; type. Although normally only values that have a single type will +;;; have an associated range, this is not enforced. The range applies +;;; to all types in the bitfield. When control flow meets, the types and +;;; ranges meet with the union operator. +;;; +;;; It is not practical to precisely compute value ranges in all cases. +;;; For example, in the following case: +;;; +;;; (let lp ((n 0)) (when (foo) (lp (1+ n)))) +;;; +;;; The first time that range analysis visits the program, N is +;;; determined to be the exact integer 0. The second time, it is an +;;; exact integer in the range [0, 1]; the third, [0, 2]; and so on. +;;; This analysis will terminate, but only after the positive half of +;;; the 32-bit range has been fully explored and we decide that the +;;; range of N is [0, +inf.0]. At the same time, we want to do range +;;; analysis and type analysis at the same time, as there are +;;; interactions between them, notably in the case of `sqrt' which +;;; returns a complex number if its argument cannot be proven to be +;;; non-negative. So what we do is, once the types reach a fixed point, +;;; we cause control-flow joins that would expand the range of a value +;;; to saturate that range towards positive or infinity (as +;;; appropriate). +;;; +;;; We represent the set of types and ranges of value at a given +;;; program point as a bytevector that is N * 12 bytes long, where N is +;;; the number of variables. Each 12-byte value indicates the type, +;;; minimum, and maximum of the value. This gives an overall time and +;;; space complexity of the algorithm of O(label-count * +;;; variable-count). Perhaps with a different representation for the +;;; types we could decrease this, sharing space between typesets and +;;; requiring fewer "meet" operations. +;;; +;;; Code: + +(define-module (language cps types) + #:use-module (ice-9 match) + #:use-module (language cps) + #:use-module (language cps dfg) + #:use-module (rnrs bytevectors) + #:export (;; Specific types. + &exact-integer + &flonum + &complex + &fraction + + &char + &unspecified + &unbound + &boolean + &nil + &null + &symbol + &keyword + + &procedure + + &pointer + &fluid + &pair + &vector + &box + &struct + &string + &bytevector + &bitvector + &array + &hash-table + + ;; Union types. + &number &real + + infer-types + lookup-pre-type + lookup-post-type + primcall-types-check?)) + +(define-syntax define-flags + (lambda (x) + (syntax-case x () + ((_ all shift name ...) + (let ((count (length #'(name ...)))) + (with-syntax (((n ...) (iota count)) + (count count)) + #'(begin + (define-syntax name (identifier-syntax (ash 1 n))) + ... + (define-syntax all (identifier-syntax (1- (ash 1 count)))) + (define-syntax shift (identifier-syntax count))))))))) + +;; More precise types have fewer bits. +(define-flags &all-types &type-bits + &exact-integer + &flonum + &complex + &fraction + + &char + &unspecified + &unbound + &boolean + &nil + &null + &symbol + &keyword + + &procedure + + &pointer + &fluid + &pair + &vector + &box + &struct + &string + &bytevector + &bitvector + &array + &hash-table) + +(define-syntax &no-type (identifier-syntax 0)) + +(define-syntax &number + (identifier-syntax (logior &exact-integer &flonum &complex &fraction))) +(define-syntax &real + (identifier-syntax (logior &exact-integer &flonum &fraction))) + +(define-syntax *max-s32* (identifier-syntax (- (ash 1 31) 1))) +(define-syntax *min-s32* (identifier-syntax (- 0 (ash 1 31)))) + +;; Versions of min and max that do not coerce exact numbers to become +;; inexact. +(define min + (case-lambda + ((a b) (if (< a b) a b)) + ((a b c) (min (min a b) c)) + ((a b c d) (min (min a b) c d)))) +(define max + (case-lambda + ((a b) (if (> a b) a b)) + ((a b c) (max (max a b) c)) + ((a b c d) (max (max a b) c d)))) + +(define (constant-type val) + "Compute the type and range of VAL. Return three values: the type, +minimum, and maximum." + (define (return type val) + (if val + (values type val val) + (values type -inf.0 +inf.0))) + (cond + ((number? val) + (cond + ((exact-integer? val) (return &exact-integer val)) + ((eqv? (imag-part val) 0) + (values (if (exact? val) &fraction &flonum) + (if (rational? val) (inexact->exact (floor val)) val) + (if (rational? val) (inexact->exact (ceiling val)) val))) + (else (return &complex #f)))) + ((eq? val '()) (return &null #f)) + ((eq? val #nil) (return &nil #f)) + ((char? val) (return &char (char->integer val))) + ((eqv? val *unspecified*) (return &unspecified #f)) + ((boolean? val) (return &boolean (if val 1 0))) + ((symbol? val) (return &symbol #f)) + ((keyword? val) (return &keyword #f)) + ((pair? val) (return &pair #f)) + ((vector? val) (return &vector (vector-length val))) + ((string? val) (return &string (string-length val))) + ((bytevector? val) (return &bytevector (bytevector-length val))) + ((bitvector? val) (return &bitvector (bitvector-length val))) + ((array? val) (return &array (array-rank val))) + ((not (variable-bound? (make-variable val))) (return &unbound #f)) + + (else (error "unhandled constant" val)))) + +(define-syntax-rule (var-type bv var) + (bytevector-u32-native-ref bv (* var 12))) +(define-syntax-rule (var-clamped-min bv var) + (bytevector-s32-native-ref bv (+ (* var 12) 4))) +(define-syntax-rule (var-clamped-max bv var) + (bytevector-s32-native-ref bv (+ (* var 12) 8))) +(define-syntax-rule (var-min bv var) + (let ((min (var-clamped-min bv var))) + (if (= min *min-s32*) + -inf.0 + min))) +(define-syntax-rule (var-max bv var) + (let ((max (var-clamped-max bv var))) + (if (= max *max-s32*) + +inf.0 + max))) + +(define-inlinable (clamp-range val) + (cond + ((< val *min-s32*) *min-s32*) + ((< *max-s32* val) *max-s32*) + (else val))) +(define-syntax-rule (set-var-type! bv var val) + (bytevector-u32-native-set! bv (* var 12) val)) +(define-syntax-rule (set-var-clamped-min! bv var val) + (bytevector-s32-native-set! bv (+ (* var 12) 4) val)) +(define-syntax-rule (set-var-clamped-max! bv var val) + (bytevector-s32-native-set! bv (+ (* var 12) 8) val)) +(define-syntax-rule (set-var-min! bv var val) + (set-var-clamped-min! bv var (clamp-range val))) +(define-syntax-rule (set-var-max! bv var val) + (set-var-clamped-max! bv var (clamp-range val))) + +(define-inlinable (extend-var-type! bv var type) + (set-var-type! bv var (logior (var-type bv var) type))) +(define-inlinable (restrict-var-type! bv var type) + (set-var-type! bv var (logand (var-type bv var) type))) +(define-inlinable (extend-var-range! bv var min max) + (let ((old-min (var-clamped-min bv var)) + (old-max (var-clamped-max bv var)) + (min (clamp-range min)) + (max (clamp-range max))) + (when (< min old-min) + (set-var-clamped-min! bv var min)) + (when (< old-max max) + (set-var-clamped-max! bv var max)))) +(define-inlinable (restrict-var-range! bv var min max) + (let ((old-min (var-clamped-min bv var)) + (old-max (var-clamped-max bv var)) + (min (clamp-range min)) + (max (clamp-range max))) + (when (< old-min min) + (set-var-clamped-min! bv var min)) + (when (< max old-max) + (set-var-clamped-max! bv var max)))) + +(define *type-checkers* (make-hash-table)) +(define *type-inferrers* (make-hash-table)) +(define *predicate-inferrers* (make-hash-table)) + +(define-syntax-rule (define-type-helper name) + (define-syntax-parameter name + (lambda (stx) + (syntax-violation 'name + "macro used outside of define-type" + stx)))) +(define-type-helper define!) +(define-type-helper restrict!) +(define-type-helper &type) +(define-type-helper &min) +(define-type-helper &max) + +(define-syntax-rule (define-type-checker (name arg ...) body ...) + (hashq-set! + *type-checkers* + 'name + (lambda (in arg ...) + (syntax-parameterize + ((&type (syntax-rules () ((_ val) (var-type in val)))) + (&min (syntax-rules () ((_ val) (var-min in val)))) + (&max (syntax-rules () ((_ val) (var-max in val))))) + body ...)))) + +(define-syntax-rule (check-type arg type min max) + ;; If the arg is negative, it is a closure variable. + (and (>= arg 0) + (zero? (logand (lognot type) (&type arg))) + (<= min (&min arg)) + (<= (&max arg) max))) + +(define-syntax-rule (define-type-inferrer (name var ...) body ...) + (hashq-set! + *type-inferrers* + 'name + (lambda (out var ...) + (syntax-parameterize + ((define! + (syntax-rules () + ((_ val type min max) + (begin + (extend-var-type! out val type) + (extend-var-range! out val min max))))) + (restrict! + (syntax-rules () + ((_ val type min max) + (when (>= val 0) + (restrict-var-type! out val type) + (restrict-var-range! out val min max))))) + ;; Negative vals are closure variables. + (&type (syntax-rules () + ((_ val) (if (< val 0) &all-types (var-type out val))))) + (&min (syntax-rules () + ((_ val) (if (< val 0) -inf.0 (var-min out val))))) + (&max (syntax-rules () + ((_ val) (if (< val 0) +inf.0 (var-max out val)))))) + body ... + (values))))) + +(define-syntax-rule (define-predicate-inferrer (name var ... true?) body ...) + (hashq-set! + *predicate-inferrers* + 'name + (lambda (out var ... true?) + (syntax-parameterize + ((restrict! + (syntax-rules () + ((_ val type min max) + (when (>= val 0) + (restrict-var-type! out val type) + (restrict-var-range! out val min max))))) + ;; Negative vals are closure variables. + (&type (syntax-rules () + ((_ val) (if (< val 0) &all-types (var-type out val))))) + (&min (syntax-rules () + ((_ val) (if (< val 0) -inf.0 (var-min out val))))) + (&max (syntax-rules () + ((_ val) (if (< val 0) +inf.0 (var-max out val)))))) + body ... + (values))))) + +(define-syntax define-simple-type-checker + (lambda (x) + (define (parse-spec l) + (syntax-case l () + (() '()) + (((type min max) . l) (cons #'(type min max) (parse-spec #'l))) + (((type min+max) . l) (cons #'(type min+max min+max) (parse-spec #'l))) + ((type . l) (cons #'(type -inf.0 +inf.0) (parse-spec #'l))))) + (syntax-case x () + ((_ (name arg-spec ...) result-spec ...) + (with-syntax + (((arg ...) (generate-temporaries #'(arg-spec ...))) + (((arg-type arg-min arg-max) ...) (parse-spec #'(arg-spec ...)))) + #'(define-type-checker (name arg ...) + (and (check-type arg arg-type arg-min arg-max) + ...))))))) + +(define-syntax define-simple-type-inferrer + (lambda (x) + (define (parse-spec l) + (syntax-case l () + (() '()) + (((type min max) . l) (cons #'(type min max) (parse-spec #'l))) + (((type min+max) . l) (cons #'(type min+max min+max) (parse-spec #'l))) + ((type . l) (cons #'(type -inf.0 +inf.0) (parse-spec #'l))))) + (syntax-case x () + ((_ (name arg-spec ...) result-spec ...) + (with-syntax + (((arg ...) (generate-temporaries #'(arg-spec ...))) + (((arg-type arg-min arg-max) ...) (parse-spec #'(arg-spec ...))) + ((res ...) (generate-temporaries #'(result-spec ...))) + (((res-type res-min res-max) ...) (parse-spec #'(result-spec ...)))) + #'(define-type-inferrer (name arg ... res ...) + (restrict! arg arg-type arg-min arg-max) + ... + (define! res res-type res-min res-max) + ...)))))) + +(define-syntax-rule (define-simple-type (name arg-spec ...) result-spec ...) + (begin + (define-simple-type-checker (name arg-spec ...)) + (define-simple-type-inferrer (name arg-spec ...) result-spec ...))) + +(define-syntax-rule (define-simple-types + ((name arg-spec ...) result-spec ...) + ...) + (begin + (define-simple-type (name arg-spec ...) result-spec ...) + ...)) + +(define-syntax-rule (define-type-checker-aliases orig alias ...) + (let ((check (hashq-ref *type-checkers* 'orig))) + (hashq-set! *type-checkers* 'alias check) + ...)) +(define-syntax-rule (define-type-inferrer-aliases orig alias ...) + (let ((check (hashq-ref *type-inferrers* 'orig))) + (hashq-set! *type-inferrers* 'alias check) + ...)) +(define-syntax-rule (define-type-aliases orig alias ...) + (begin + (define-type-checker-aliases orig alias ...) + (define-type-inferrer-aliases orig alias ...))) + + + + +;;; This list of primcall type definitions follows the order of +;;; effects-analysis.scm; please keep it in a similar order. +;;; +;;; There is no need to add checker definitions for expressions that do +;;; not exhibit the &type-check effect, as callers should not ask if +;;; such an expression does or does not type-check. For those that do +;;; exhibit &type-check, you should define a type inferrer unless the +;;; primcall will never typecheck. +;;; +;;; Likewise there is no need to define inferrers for primcalls which +;;; return &all-types values and which never raise exceptions from which +;;; we can infer the types of incoming values. + + + + +;;; +;;; Miscellaneous. +;;; + +(define-simple-type-checker (not &all-types)) +(define-type-inferrer (not val result) + (cond + ((and (eqv? (&type val) &boolean) + (eqv? (&min val) (&max val))) + (let ((val (if (zero? (&min val)) 1 0))) + (define! result &boolean val val))) + (else + (define! result &boolean 0 1)))) + + + + +;;; +;;; Generic effect-free predicates. +;;; + +(define-predicate-inferrer (eq? a b true?) + ;; We can only propagate information down the true leg. + (when true? + (let ((type (logand (&type a) (&type b))) + (min (max (&min a) (&min b))) + (max (min (&max a) (&max b)))) + (restrict! a type min max) + (restrict! b type min max)))) +(define-type-inferrer-aliases eq? eqv? equal?) + +(define-syntax-rule (define-simple-predicate-inferrer predicate type) + (define-predicate-inferrer (predicate val true?) + (let ((type (if true? + type + (logand (&type val) (lognot type))))) + (restrict! val type -inf.0 +inf.0)))) +(define-simple-predicate-inferrer pair? &pair) +(define-simple-predicate-inferrer null? &null) +(define-simple-predicate-inferrer nil? &nil) +(define-simple-predicate-inferrer symbol? &symbol) +(define-simple-predicate-inferrer variable? &box) +(define-simple-predicate-inferrer vector? &vector) +(define-simple-predicate-inferrer struct? &struct) +(define-simple-predicate-inferrer string? &string) +(define-simple-predicate-inferrer number? &number) +(define-simple-predicate-inferrer char? &char) +(define-simple-predicate-inferrer procedure? &procedure) +(define-simple-predicate-inferrer thunk? &procedure) + + + +;;; +;;; Fluids. Note that we can't track bound-ness of fluids, as pop-fluid +;;; can change boundness. +;;; + +(define-simple-types + ((fluid-ref (&fluid 1)) &all-types) + ((fluid-set! (&fluid 0 1) &all-types)) + ((push-fluid (&fluid 0 1) &all-types)) + ((pop-fluid))) + + + + +;;; +;;; Prompts. (Nothing to do.) +;;; + + + + +;;; +;;; Pairs. +;;; + +(define-simple-types + ((cons &all-types &all-types) &pair) + ((car &pair) &all-types) + ((set-car! &pair &all-types)) + ((cdr &pair) &all-types) + ((set-cdr! &pair &all-types))) + + + + +;;; +;;; Variables. +;;; + +(define-simple-types + ((box &all-types) (&box 1)) + ((box-ref (&box 1)) &all-types)) + +(define-simple-type-checker (box-set! (&box 0 1) &all-types)) +(define-type-inferrer (box-set! box val) + (restrict! box &box 1 1)) + + + + +;;; +;;; Vectors. +;;; + +;; This max-vector-len computation is a hack. +(define *max-vector-len* (ash most-positive-fixnum -5)) + +(define-simple-type-checker (make-vector (&exact-integer 0 *max-vector-len*) + &all-types)) +(define-type-inferrer (make-vector size init result) + (restrict! size &exact-integer 0 *max-vector-len*) + (define! result &vector (&min size) (&max size))) + +(define-type-checker (vector-ref v idx) + (and (check-type v &vector 0 *max-vector-len*) + (check-type idx &exact-integer 0 (1- (&min v))))) +(define-type-inferrer (vector-ref v idx result) + (restrict! v &vector (1+ (&min idx)) +inf.0) + (restrict! idx &exact-integer 0 (1- (&max v))) + (define! result &all-types -inf.0 +inf.0)) + +(define-type-checker (vector-set! v idx val) + (and (check-type v &vector 0 *max-vector-len*) + (check-type idx &exact-integer 0 (1- (&min v))))) +(define-type-inferrer (vector-set! v idx val) + (restrict! v &vector (1+ (&min idx)) +inf.0) + (restrict! idx &exact-integer 0 (1- (&max v)))) + +(define-type-aliases make-vector make-vector/immediate) +(define-type-aliases vector-ref vector-ref/immediate) +(define-type-aliases vector-set! vector-set!/immediate) + +(define-simple-type-checker (vector-length &vector)) +(define-type-inferrer (vector-length v result) + (restrict! v &vector 0 *max-vector-len*) + (define! result &exact-integer (max (&min v) 0) (&max v))) + + + + +;;; +;;; Structs. +;;; + +;; No type-checker for allocate-struct, as we can't currently check that +;; vt is actually a vtable. +(define-type-inferrer (allocate-struct vt size result) + (restrict! vt &struct vtable-offset-user +inf.0) + (restrict! size &exact-integer 0 +inf.0) + (define! result &struct (max (&min size) 0) (&max size))) + +(define-type-checker (struct-ref s idx) + (and (check-type s &struct 0 +inf.0) + (check-type idx &exact-integer 0 +inf.0) + ;; FIXME: is the field readable? + (< (&max idx) (&min s)))) +(define-type-inferrer (struct-ref s idx result) + (restrict! s &struct (1+ (&min idx)) +inf.0) + (restrict! idx &exact-integer 0 (1- (&max s))) + (define! result &all-types -inf.0 +inf.0)) + +(define-type-checker (struct-set! s idx val) + (and (check-type s &struct 0 +inf.0) + (check-type idx &exact-integer 0 +inf.0) + ;; FIXME: is the field writable? + (< (&max idx) (&min s)))) +(define-type-inferrer (struct-set! s idx val) + (restrict! s &struct (1+ (&min idx)) +inf.0) + (restrict! idx &exact-integer 0 (1- (&max s)))) + +(define-type-aliases allocate-struct allocate-struct/immediate) +(define-type-aliases struct-ref struct-ref/immediate) +(define-type-aliases struct-set! struct-set!/immediate) + +(define-simple-type (struct-vtable (&struct 0 +inf.0)) + (&struct vtable-offset-user +inf.0)) + + + + +;;; +;;; Strings. +;;; + +(define *max-char* (1- (ash 1 24))) + +(define-type-checker (string-ref s idx) + (and (check-type s &string 0 +inf.0) + (check-type idx &exact-integer 0 +inf.0) + (< (&max idx) (&min s)))) +(define-type-inferrer (string-ref s idx result) + (restrict! s &string (1+ (&min idx)) +inf.0) + (restrict! idx &exact-integer 0 (1- (&max s))) + (define! result &char 0 *max-char*)) + +(define-type-checker (string-set! s idx val) + (and (check-type s &string 0 +inf.0) + (check-type idx &exact-integer 0 +inf.0) + (check-type val &char 0 *max-char*) + (< (&max idx) (&min s)))) +(define-type-inferrer (string-set! s idx val) + (restrict! s &string (1+ (&min idx)) +inf.0) + (restrict! idx &exact-integer 0 (1- (&max s))) + (restrict! val &char 0 *max-char*)) + +(define-simple-type-checker (string-length &string)) +(define-type-inferrer (string-length s result) + (restrict! s &string 0 +inf.0) + (define! result &exact-integer (max (&min s) 0) (&max s))) + +(define-simple-type (number->string &number) (&string 0 +inf.0)) +(define-simple-type (string->number (&string 0 +inf.0)) + ((logior &number &boolean) -inf.0 +inf.0)) + + + + +;;; +;;; Bytevectors. +;;; + +(define-simple-type-checker (bytevector-length &bytevector)) +(define-type-inferrer (bytevector-length bv result) + (restrict! bv &bytevector 0 +inf.0) + (define! result &exact-integer (max (&min bv) 0) (&max bv))) + +(define-syntax-rule (define-bytevector-accessors ref set type size min max) + (begin + (define-type-checker (ref bv idx) + (and (check-type bv &bytevector 0 +inf.0) + (check-type idx &exact-integer 0 +inf.0) + (< (&max idx) (- (&min bv) size)))) + (define-type-inferrer (ref bv idx result) + (restrict! bv &bytevector (+ (&min idx) size) +inf.0) + (restrict! idx &exact-integer 0 (- (&max bv) size)) + (define! result type min max)) + (define-type-checker (set bv idx val) + (and (check-type bv &bytevector 0 +inf.0) + (check-type idx &exact-integer 0 +inf.0) + (check-type val type min max) + (< (&max idx) (- (&min bv) size)))) + (define-type-inferrer (set! bv idx val) + (restrict! bv &bytevector (+ (&min idx) size) +inf.0) + (restrict! idx &exact-integer 0 (- (&max bv) size)) + (restrict! val type min max)))) + +(define-syntax-rule (define-short-bytevector-accessors ref set size signed?) + (define-bytevector-accessors ref set &exact-integer size + (if signed? (- (ash 1 (1- (* size 8)))) 0) + (1- (ash 1 (if signed? (1- (* size 8)) (* size 8)))))) + +(define-short-bytevector-accessors bv-u8-ref bv-u8-set! 1 #f) +(define-short-bytevector-accessors bv-s8-ref bv-s8-set! 1 #t) +(define-short-bytevector-accessors bv-u16-ref bv-u16-set! 2 #f) +(define-short-bytevector-accessors bv-s16-ref bv-s16-set! 2 #t) + +;; The range analysis only works on signed 32-bit values, so some limits +;; are out of range. +(define-bytevector-accessors bv-u32-ref bv-u32-set! &exact-integer 4 0 +inf.0) +(define-bytevector-accessors bv-s32-ref bv-s32-set! &exact-integer 4 -inf.0 +inf.0) +(define-bytevector-accessors bv-u64-ref bv-u64-set! &exact-integer 8 0 +inf.0) +(define-bytevector-accessors bv-s64-ref bv-s64-set! &exact-integer 8 -inf.0 +inf.0) +(define-bytevector-accessors bv-f32-ref bv-f32-set! &real 4 -inf.0 +inf.0) +(define-bytevector-accessors bv-f64-ref bv-f64-set! &real 8 -inf.0 +inf.0) + + + + +;;; +;;; Numbers. +;;; + +;; First, branching primitives with no results. +(define-simple-type-checker (= &number &number)) +(define-predicate-inferrer (= a b true?) + (when (and true? + (zero? (logand (logior (&type a) (&type b)) (lognot &number)))) + (let ((min (max (&min a) (&min b))) + (max (min (&max a) (&max b)))) + (restrict! a &number min max) + (restrict! b &number min max)))) + +(define-simple-type-checker (< &real &real)) +(define-predicate-inferrer (< a b true?) + (when (zero? (logand (logior (&type a) (&type b)) (lognot &number))) + (restrict! a &real -inf.0 +inf.0) + (restrict! b &real -inf.0 +inf.0))) +(define-type-aliases < <= > >=) + +;; Arithmetic. +(define-syntax-rule (define-unary-result! a result min max) + (let ((min* min) + (max* max) + (type (logand (&type a) &number))) + (cond + ((not (= type (&type a))) + ;; Not a number. Punt and do nothing. + (define! result &all-types -inf.0 +inf.0)) + ;; Complex numbers don't have a range. + ((eqv? type &complex) + (define! result &complex -inf.0 +inf.0)) + (else + (define! result type min* max*))))) + +(define-syntax-rule (define-binary-result! a b result closed? min max) + (let ((min* min) + (max* max) + (a-type (logand (&type a) &number)) + (b-type (logand (&type b) &number))) + (cond + ((or (not (= a-type (&type a))) (not (= b-type (&type b)))) + ;; One input not a number. Perhaps we end up dispatching to + ;; GOOPS. + (define! result &all-types -inf.0 +inf.0)) + ;; Complex and floating-point numbers are contagious. + ((or (eqv? a-type &complex) (eqv? b-type &complex)) + (define! result &complex -inf.0 +inf.0)) + ((or (eqv? a-type &flonum) (eqv? b-type &flonum)) + (define! result &flonum min* max*)) + ;; Exact integers are closed under some operations. + ((and closed? (eqv? a-type &exact-integer) (eqv? b-type &exact-integer)) + (define! result &exact-integer min* max*)) + (else + ;; Fractions may become integers. + (let ((type (logior a-type b-type))) + (define! result + (if (zero? (logand type &fraction)) + type + (logior type &exact-integer)) + min* max*)))))) + +(define-simple-type-checker (add &number &number)) +(define-type-inferrer (add a b result) + (define-binary-result! a b result #t + (+ (&min a) (&min b)) + (+ (&max a) (&max b)))) + +(define-simple-type-checker (sub &number &number)) +(define-type-inferrer (sub a b result) + (define-binary-result! a b result #t + (- (&min a) (&max b)) + (- (&max a) (&min b)))) + +(define-simple-type-checker (mul &number &number)) +(define-type-inferrer (mul a b result) + (let ((min-a (&min a)) (max-a (&max a)) + (min-b (&min b)) (max-b (&max b))) + (let ((-- (* min-a min-b)) + (-+ (* min-a max-b)) + (++ (* max-a max-b)) + (+- (* max-a min-b))) + (define-binary-result! a b result #t + (if (eqv? a b) 0 (min -- -+ ++ +-)) + (max -- -+ ++ +-))))) + +(define-type-checker (div a b) + (and (check-type a &number -inf.0 +inf.0) + (check-type b &number -inf.0 +inf.0) + ;; We only know that there will not be an exception if b is not + ;; zero. + (not (<= (&min b) 0 (&max b))))) +(define-type-inferrer (div a b result) + (let ((min-a (&min a)) (max-a (&max a)) + (min-b (&min b)) (max-b (&max b))) + (call-with-values + (lambda () + (if (<= min-b 0 max-b) + ;; If the range of the divisor crosses 0, the result spans + ;; the whole range. + (values -inf.0 +inf.0) + ;; Otherwise min-b and max-b have the same sign, and cannot both + ;; be infinity. + (let ((-- (if (inf? min-b) 0 (* min-a min-b))) + (-+ (if (inf? max-b) 0 (* min-a max-b))) + (++ (if (inf? max-b) 0 (* max-a max-b))) + (+- (if (inf? min-b) 0 (* max-a min-b)))) + (values (min -- -+ ++ +-) + (max -- -+ ++ +-))))) + (lambda (min max) + (define-binary-result! a b result #f min max))))) + +(define-simple-type-checker (add1 &number)) +(define-type-inferrer (add1 a result) + (define-unary-result! a result (1+ (&min a)) (1+ (&max a)))) + +(define-simple-type-checker (sub1 &number)) +(define-type-inferrer (sub1 a result) + (define-unary-result! a result (1- (&min a)) (1- (&max a)))) + +(define-type-checker (quo a b) + (and (check-type a &exact-integer -inf.0 +inf.0) + (check-type b &exact-integer -inf.0 +inf.0) + ;; We only know that there will not be an exception if b is not + ;; zero. + (not (<= (&min b) 0 (&max b))))) +(define-type-inferrer (quo a b result) + (restrict! a &exact-integer -inf.0 +inf.0) + (restrict! b &exact-integer -inf.0 +inf.0) + (define! result &exact-integer -inf.0 +inf.0)) + +(define-type-checker-aliases quo rem) +(define-type-inferrer (rem a b result) + (restrict! a &exact-integer -inf.0 +inf.0) + (restrict! b &exact-integer -inf.0 +inf.0) + ;; Same sign as A. + (let ((max-abs-rem (1- (max (abs (&min b)) (abs (&max b)))))) + (cond + ((< (&min a) 0) + (if (< 0 (&max a)) + (define! result &exact-integer (- max-abs-rem) max-abs-rem) + (define! result &exact-integer (- max-abs-rem) 0))) + (else + (define! result &exact-integer 0 max-abs-rem))))) + +(define-type-checker-aliases quo mod) +(define-type-inferrer (mod a b result) + (restrict! a &exact-integer -inf.0 +inf.0) + (restrict! b &exact-integer -inf.0 +inf.0) + ;; Same sign as B. + (let ((max-abs-mod (1- (max (abs (&min b)) (abs (&max b)))))) + (cond + ((< (&min b) 0) + (if (< 0 (&max b)) + (define! result &exact-integer (- max-abs-mod) max-abs-mod) + (define! result &exact-integer (- max-abs-mod) 0))) + (else + (define! result &exact-integer 0 max-abs-mod))))) + +;; Predicates. +(define-syntax-rule (define-number-kind-predicate-inferrer name type) + (define-type-inferrer (name val result) + (cond + ((zero? (logand (&type val) type)) + (define! result &boolean 0 0)) + ((zero? (logand (&type val) (lognot type))) + (define! result &boolean 1 1)) + (else + (define! result &boolean 0 1))))) +(define-number-kind-predicate-inferrer complex? &number) +(define-number-kind-predicate-inferrer real? &real) +(define-number-kind-predicate-inferrer rational? + (logior &exact-integer &fraction)) +(define-number-kind-predicate-inferrer integer? + (logior &exact-integer &flonum)) +(define-number-kind-predicate-inferrer exact-integer? + &exact-integer) + +(define-simple-type-checker (exact? &number)) +(define-type-inferrer (exact? val result) + (restrict! val &number -inf.0 +inf.0) + (cond + ((zero? (logand (&type val) (logior &exact-integer &fraction))) + (define! result &boolean 0 0)) + ((zero? (logand (&type val) (lognot (logior &exact-integer &fraction)))) + (define! result &boolean 1 1)) + (else + (define! result &boolean 0 1)))) + +(define-simple-type-checker (inexact? &number)) +(define-type-inferrer (inexact? val result) + (restrict! val &number -inf.0 +inf.0) + (cond + ((zero? (logand (&type val) (logior &flonum &complex))) + (define! result &boolean 0 0)) + ((zero? (logand (&type val) (lognot (logior &flonum &complex)))) + (define! result &boolean 1 1)) + (else + (define! result &boolean 0 1)))) + +(define-simple-type-checker (inf? &real)) +(define-type-inferrer (inf? val result) + (restrict! val &real -inf.0 +inf.0) + (cond + ((or (zero? (logand (&type val) (logior &flonum &complex))) + (and (not (inf? (&min val))) (not (inf? (&max val))))) + (define! result &boolean 0 0)) + (else + (define! result &boolean 0 1)))) + +(define-type-aliases inf? nan?) + +(define-simple-type (even? &exact-integer) (&boolean 0 1)) +(define-type-aliases even? odd?) + +;; Bit operations. +(define-simple-type-checker (ash &exact-integer &exact-integer)) +(define-type-inferrer (ash val count result) + (define (ash* val count) + ;; As we can only represent a 32-bit range, don't bother inferring + ;; shifts that might exceed that range. + (cond + ((inf? val) val) ; Preserves sign. + ((< -32 count 32) (ash val count)) + ((zero? val) 0) + ((positive? val) +inf.0) + (else -inf.0))) + (restrict! val &exact-integer -inf.0 +inf.0) + (restrict! count &exact-integer -inf.0 +inf.0) + (let ((-- (ash* (&min val) (&min count))) + (-+ (ash* (&min val) (&max count))) + (++ (ash* (&max val) (&max count))) + (+- (ash* (&max val) (&min count)))) + (define! result &exact-integer + (min -- -+ ++ +-) + (max -- -+ ++ +-)))) + +(define (next-power-of-two n) + (let lp ((out 1)) + (if (< n out) + out + (lp (ash out 1))))) + +(define-simple-type-checker (logand &exact-integer &exact-integer)) +(define-type-inferrer (logand a b result) + (define (logand-min a b) + (if (< a b 0) + (min a b) + 0)) + (define (logand-max a b) + (if (< a b 0) + 0 + (max a b))) + (restrict! a &exact-integer -inf.0 +inf.0) + (restrict! b &exact-integer -inf.0 +inf.0) + (define! result &exact-integer + (logand-min (&min a) (&min b)) + (logand-max (&max a) (&max b)))) + +(define-simple-type-checker (logior &exact-integer &exact-integer)) +(define-type-inferrer (logior a b result) + ;; Saturate all bits of val. + (define (saturate val) + (1- (next-power-of-two val))) + (define (logior-min a b) + (cond ((and (< a 0) (<= 0 b)) a) + ((and (< b 0) (<= 0 a)) b) + (else (max a b)))) + (define (logior-max a b) + ;; If either operand is negative, just assume the max is -1. + (cond + ((or (< a 0) (< b 0)) -1) + ((or (inf? a) (inf? b)) +inf.0) + (else (saturate (logior a b))))) + (restrict! a &exact-integer -inf.0 +inf.0) + (restrict! b &exact-integer -inf.0 +inf.0) + (define! result &exact-integer + (logior-min (&min a) (&min b)) + (logior-max (&max a) (&max b)))) + +;; For our purposes, treat logxor the same as logior. +(define-type-aliases logior logxor) + +(define-simple-type-checker (lognot &exact-integer)) +(define-type-inferrer (lognot a result) + (restrict! a &exact-integer -inf.0 +inf.0) + (define! result &exact-integer + (- -1 (&max a)) + (- -1 (&min a)))) + +;; Flonums. +(define-simple-type-checker (sqrt &number)) +(define-type-inferrer (sqrt x result) + (restrict! x &number -inf.0 +inf.0) + (let ((type (&type x))) + (cond + ((and (zero? (logand type &complex)) (<= 0 (&min x))) + (define! result + (logior type &flonum) + (inexact->exact (floor (sqrt (&min x)))) + (if (inf? (&max x)) + +inf.0 + (inexact->exact (ceiling (sqrt (&max x))))))) + (else + (define! result (logior type &flonum &complex) -inf.0 +inf.0))))) + +(define-simple-type-checker (abs &real)) +(define-type-inferrer (abs x result) + (restrict! x &real -inf.0 +inf.0) + (define! result (logior (logand (&type x) (lognot &number)) + (logand (&type x) &real)) + (min (abs (&min x)) (abs (&max x))) + (max (abs (&min x)) (abs (&max x))))) + + + + +;;; +;;; Characters. +;;; + +(define-simple-type (char=? char>?) + +(define-simple-type-checker (integer->char (&exact-integer 0 #x10ffff))) +(define-type-inferrer (integer->char i result) + (restrict! i &exact-integer 0 #x10ffff) + (define! result &char (&min i) (&max i))) + +(define-simple-type-checker (char->integer &char)) +(define-type-inferrer (char->integer c result) + (restrict! c &char 0 #x10ffff) + (define! result &exact-integer (&min c) (&max c))) + + + + +;;; +;;; Type flow analysis: the meet (ahem) of the algorithm. +;;; + +(define (infer-types* dfg min-label label-count min-var var-count) + "Compute types for all variables in @var{fun}. Returns a hash table +mapping symbols to types." + (let* ((typev (make-vector (* 2 label-count) #f)) + (changed (make-bitvector var-count #f)) + (changed-types (make-bitvector var-count #f)) + (changed-ranges (make-bitvector var-count #f)) + (revisit-labels (make-bitvector label-count #f)) + (tmp (make-bytevector (* var-count 12) 0)) + (tmp2 (make-bytevector (* var-count 12) 0)) + (saturate? #f)) + (define (var->idx var) (- var min-var)) + (define (idx->var idx) (+ idx min-var)) + (define (label->idx label) (- label min-label)) + (define (idx->label idx) (+ idx min-label)) + + (define (get-pre-types label) + (vector-ref typev (* (label->idx label) 2))) + (define (get-post-types label) + (vector-ref typev (1+ (* (label->idx label) 2)))) + + (define (define! bv val type min max) + (extend-var-type! bv val type) + (extend-var-range! bv val min max)) + + (define (restrict! bv val type min max) + (when (>= val 0) + (restrict-var-type! bv val type) + (restrict-var-range! bv val min max))) + + (define (infer-primcall! out name args result) + (let lp ((args args)) + (match args + ((arg . args) + ;; Primcall operands can originate outside the function. + (when (<= 0 arg) + (bitvector-set! changed arg #t)) + (lp args)) + (_ #f))) + (when result + (bitvector-set! changed result #t)) + (let ((inferrer (hashq-ref *type-inferrers* name))) + (if inferrer + ;; FIXME: remove the apply? + (apply inferrer out + (if result + (append args (list result)) + args)) + (when result + (define! out result &all-types -inf.0 +inf.0))))) + + (define (infer-predicate! out name args true?) + (let ((pred-inferrer (hashq-ref *predicate-inferrers* name))) + (when pred-inferrer + ;; FIXME: remove the apply? + (apply pred-inferrer out (append args (list true?)))))) + + (define (propagate-types! k in) + (match (lookup-predecessors k dfg) + ((_) + ;; Fast path: we dominate the successor. Just copy; there's no + ;; need to set bits in the "revisit-labels" set because we'll + ;; reach the successor in this iteration anyway. + (let ((out (get-pre-types k))) + (bytevector-copy! in 0 out 0 (* var-count 12)) + out)) + (_ + (propagate-types/slow! k in)))) + + (define (propagate-types/slow! k in) + (let ((out (get-pre-types k))) + ;; Slow path: union. + (let lp ((n 0)) + (let ((n (bit-position #t changed-types n))) + (when n + (let ((in-type (var-type in n)) + (out-type (var-type out n))) + (let ((type (logior in-type out-type))) + (unless (= type out-type) + (bitvector-set! revisit-labels (label->idx k) #t) + (set-var-type! out n type)))) + (lp (1+ n))))) + (let lp ((n 0)) + (let ((n (bit-position #t changed-ranges n))) + (when n + (let ((in-min (var-clamped-min in n)) + (in-max (var-clamped-max in n)) + (out-min (var-clamped-min out n)) + (out-max (var-clamped-max out n))) + (let ((min (min in-min out-min))) + (unless (= min out-min) + (bitvector-set! revisit-labels (label->idx k) #t) + (set-var-min! out n (if saturate? *min-s32* min)))) + (let ((max (max in-max out-max))) + (unless (= max out-max) + (bitvector-set! revisit-labels (label->idx k) #t) + (set-var-max! out n (if saturate? *max-s32* max))))) + (lp (1+ n))))))) + + ;; Initialize "tmp" as a template. + (let lp ((n 0)) + (when (< n var-count) + (set-var-min! tmp n +inf.0) + (set-var-max! tmp n -inf.0) + (lp (1+ n)))) + + ;; Initial state: invalid range, no types. + (let lp ((n 0)) + (define (make-fresh-type-vector var-count) + (let ((bv (make-bytevector (* var-count 12) 0))) + (bytevector-copy! tmp 0 bv 0 (* var-count 12)) + bv)) + (when (< n label-count) + (vector-set! typev (* n 2) (make-fresh-type-vector var-count)) + (vector-set! typev (1+ (* n 2)) (make-fresh-type-vector var-count)) + (lp (1+ n)))) + + ;; Iterate over all labels in the function. When visiting a label + ;; N, we first propagate N's types to the continuation, then refine + ;; those types in place (at the continuation). This is consistent + ;; with an interpretation that the types at a labelled expression + ;; describe the values before the expression is evaluated, i.e., the + ;; types that flow into a label. + (let lp ((label min-label)) + (cond + ((< label (+ min-label label-count)) + (let ((pre (get-pre-types label)) + (post (get-post-types label))) + ;; First, clear the "changed" bitvector and save a copy of the + ;; "post" set, so we can detect what changes in this + ;; expression. + (let ((revisit? (bitvector-ref revisit-labels (label->idx label)))) + ;; Check all variables for changes in expressions that we + ;; are revisiting because of a changed incoming type or + ;; range on a control-flow join. + (bitvector-fill! changed revisit?)) + (bitvector-set! revisit-labels (label->idx label) #f) + (bytevector-copy! post 0 tmp 0 (bytevector-length post)) + + ;; Now copy the incoming types to the outgoing types. + (bytevector-copy! pre 0 post 0 (bytevector-length post)) + + ;; Add types for new definitions, and restrict types of + ;; existing variables due to side effects. + (match (lookup-cont label dfg) + ;; fixme: letrec + (($ $kargs names vars term) + (let visit-term ((term term)) + (match term + (($ $letrec names vars funs term) + (let lp ((vars vars)) + (match vars + ((var . vars) + (let ((def (var->idx var))) + (bitvector-set! changed def #t) + (define! post def &procedure -inf.0 +inf.0) + (lp vars))) + (_ (visit-term term))))) + (($ $letk conts term) + (visit-term term)) + (($ $continue k src exp) + (match exp + (($ $primcall name args) + (match (lookup-cont k dfg) + (($ $kargs (_) (var)) + (let ((def (var->idx var))) + (infer-primcall! post name (map var->idx args) def))) + ((or ($ $kargs ()) ($ $kif)) + (infer-primcall! post name (map var->idx args) #f)) + (_ #f))) + (($ $values args) + (match (lookup-cont k dfg) + (($ $kargs _ defs) + (let lp ((defs defs) (args args)) + (match (cons defs args) + ((() . ()) #f) + (((def . defs) . (arg . args)) + (let ((def (var->idx def)) (arg (var->idx arg))) + (bitvector-set! changed def #t) + (if (< arg 0) + (define! post def &all-types -inf.0 +inf.0) + (define! post def (var-type post arg) + (var-min post arg) (var-max post arg)))) + (lp defs args))))) + (_ #f))) + ((or ($ $call) ($ $callk) ($ $prompt)) + ;; Nothing to do. + #t) + (_ + (call-with-values + (lambda () + (match exp + (($ $void) + (values &unspecified -inf.0 +inf.0)) + (($ $const val) + (constant-type val)) + ((or ($ $prim) ($ $fun) ($ $closure)) + ;; Could be more precise here. + (values &procedure -inf.0 +inf.0)))) + (lambda (type min max) + (match (lookup-cont k dfg) + (($ $kargs (_) (var)) + (let ((def (var->idx var))) + (bitvector-set! changed def #t) + (define! post def type min max)))))))))))) + (cont + (let lp ((vars (match cont + (($ $kreceive arity k*) + (match (lookup-cont k* dfg) + (($ $kargs names vars) vars))) + (($ $kfun src meta self) + (list self)) + (($ $kclause arity ($ $cont kbody)) + (match (lookup-cont kbody dfg) + (($ $kargs names vars) vars))) + (_ '())))) + (match vars + (() #t) + ((var . vars) + (bitvector-set! changed (var->idx var) #t) + (define! post (var->idx var) &all-types -inf.0 +inf.0) + (lp vars)))))) + + ;; Now determine the set of changed variables. + (let lp ((n 0)) + (let ((n (bit-position #t changed n))) + (when n + (unless (eqv? (var-type tmp n) (var-type post n)) + (bitvector-set! changed-types n #t)) + (unless (and (eqv? (var-clamped-min tmp n) + (var-clamped-min post n)) + (eqv? (var-clamped-max tmp n) + (var-clamped-max post n))) + (bitvector-set! changed-ranges n #t)) + (lp (1+ n))))) + + ;; Propagate outgoing types to successors. + (match (lookup-cont label dfg) + (($ $kargs names vars term) + (match (find-call term) + (($ $continue k src exp) + (propagate-types! k post) + (match exp + (($ $prompt escape? tag handler) + (propagate-types! handler post)) + (_ #f)) + (match (lookup-cont k dfg) + ;; We propagate one step farther for conditionals. + ;; Unfortunately we have to duplicate the + ;; changed-types logic. This is unavoidable as a $kif + ;; node has two successors but only one post-types + ;; set. + (($ $kif kt kf) + (let ((kt-out tmp) + (kf-out tmp2)) + (define (update-changelist! k from var) + (let ((to (get-pre-types k))) + (unless (or (< var 0) + (bitvector-ref changed-types var) + (= (logior (var-type from var) + (var-type to var)) + (var-type to var))) + (bitvector-set! changed-types var #t)) + (unless (or (< var 0) + (bitvector-ref changed-ranges var) + (and + (<= (var-min to var) (var-min from var)) + (<= (var-max from var) (var-max to var)))) + (bitvector-set! changed-ranges var #t)))) + (bytevector-copy! post 0 kt-out 0 (bytevector-length post)) + (bytevector-copy! post 0 kf-out 0 (bytevector-length post)) + (let lp ((args (match exp + (($ $values (arg)) + (let* ((arg (var->idx arg))) + (restrict! kf-out arg + (logior &boolean &nil) 0 0) + (list arg))) + (($ $primcall name args) + (let ((args (map var->idx args))) + (infer-predicate! kt-out name args #t) + (infer-predicate! kf-out name args #f) + args))))) + (match args + ((arg . args) + (update-changelist! kt kt-out arg) + (update-changelist! kf kf-out arg) + (lp args)) + (_ #f))) + ;; Although "k" might dominate "kt", it's not + ;; necessarily the case that "label" dominates + ;; "kt". The perils of lookahead. + (propagate-types/slow! kt kt-out) + (propagate-types/slow! kf kf-out))) + (_ #f))))) + (($ $kreceive arity k*) + (propagate-types! k* post)) + (($ $kfun src meta self tail clause) + (let lp ((clause clause)) + (match clause + (#f #f) + (($ $cont k ($ $kclause arity body alternate)) + (propagate-types! k post) + (lp alternate))))) + (($ $kclause arity ($ $cont kbody)) + (propagate-types! kbody post)) + (_ #f))) + + ;; And loop. + (lp (1+ label))) + + ;; Iterate until the types reach a fixed point. + ((bit-position #t changed-types 0) + (bitvector-fill! changed-types #f) + (bitvector-fill! changed-ranges #f) + (lp min-label)) + + ;; Once the types have a fixed point, iterate until ranges also + ;; reach a fixed point, saturating ranges to accelerate + ;; convergence. + ((or (bit-position #t changed-ranges 0) + (bit-position #t revisit-labels 0)) + (bitvector-fill! changed-ranges #f) + (set! saturate? #t) + (lp min-label)) + + ;; All done! Return the computed types. + (else typev))))) + +(define (infer-types fun dfg) + ;; Fun must be renumbered. + (match fun + (($ $cont min-label ($ $kfun _ _ min-var)) + (call-with-values + (lambda () + ((make-local-cont-folder label-count var-count) + (lambda (k cont label-count var-count) + (define (min* var vars) + (match vars + ((var* . vars) + (min* (min var var*) vars)) + (_ var))) + (let ((label-count (1+ label-count))) + (match cont + (($ $kargs names vars body) + (let lp ((body body) + (var-count (+ var-count (length vars)))) + (match body + (($ $letrec names vars funs body) + (lp body + (+ var-count (length vars)))) + (($ $letk conts body) + (lp body var-count)) + (_ (values label-count var-count))))) + (($ $kfun src meta self) + (values label-count (1+ var-count))) + (_ + (values label-count var-count))))) + fun 0 0)) + (lambda (label-count var-count) + (infer-types* dfg min-label label-count min-var var-count)))))) + +(define (lookup-pre-type typev label def) + (if (< def 0) + (values &all-types -inf.0 +inf.0) + (let ((types (vector-ref typev (* label 2)))) + (values (var-type types def) + (var-min types def) + (var-max types def))))) + +(define (lookup-post-type typev label def) + (if (< def 0) + (values &all-types -inf.0 +inf.0) + (let ((types (vector-ref typev (1+ (* label 2))))) + (values (var-type types def) + (var-min types def) + (var-max types def))))) + +(define (primcall-types-check? label-idx typev name arg-idxs) + (let ((checker (hashq-ref *type-checkers* name))) + (and checker + (apply checker (vector-ref typev (* label-idx 2)) arg-idxs))))