372 lines
10 KiB
Factor
Executable File
372 lines
10 KiB
Factor
Executable File
! Copyright (C) 2004, 2008 Slava Pestov.
|
|
! See http://factorcode.org/license.txt for BSD license.
|
|
USING: arrays generic assocs hashtables inference kernel
|
|
math namespaces sequences words parser math.intervals
|
|
effects classes classes.algebra inference.dataflow
|
|
inference.backend combinators accessors ;
|
|
IN: inference.class
|
|
|
|
! Class inference
|
|
|
|
! A constraint is a statement about a value.
|
|
|
|
! We need a notion of equality which doesn't recurse so cannot
|
|
! infinite loop on circular data
|
|
GENERIC: eql? ( obj1 obj2 -- ? )
|
|
M: object eql? eq? ;
|
|
M: number eql? number= ;
|
|
|
|
! Maps constraints to constraints
|
|
SYMBOL: constraints
|
|
|
|
TUPLE: literal-constraint literal value ;
|
|
|
|
C: <literal-constraint> literal-constraint
|
|
|
|
M: literal-constraint equal?
|
|
over literal-constraint? [
|
|
[ [ literal>> ] bi@ eql? ]
|
|
[ [ value>> ] bi@ = ]
|
|
2bi and
|
|
] [ 2drop f ] if ;
|
|
|
|
TUPLE: class-constraint class value ;
|
|
|
|
C: <class-constraint> class-constraint
|
|
|
|
TUPLE: interval-constraint interval value ;
|
|
|
|
C: <interval-constraint> interval-constraint
|
|
|
|
GENERIC: apply-constraint ( constraint -- )
|
|
GENERIC: constraint-satisfied? ( constraint -- ? )
|
|
|
|
: `input ( n -- value ) node get in-d>> nth ;
|
|
: `output ( n -- value ) node get out-d>> nth ;
|
|
: class, ( class value -- ) <class-constraint> , ;
|
|
: literal, ( literal value -- ) <literal-constraint> , ;
|
|
: interval, ( interval value -- ) <interval-constraint> , ;
|
|
|
|
M: f apply-constraint drop ;
|
|
|
|
: make-constraints ( node quot -- constraint )
|
|
[ swap node set call ] { } make ; inline
|
|
|
|
: set-constraints ( node quot -- )
|
|
make-constraints
|
|
unclip [ 2array ] reduce
|
|
apply-constraint ; inline
|
|
|
|
: assume ( constraint -- )
|
|
constraints get at [ apply-constraint ] when* ;
|
|
|
|
! Variables used by the class inferencer
|
|
|
|
! Current value --> literal mapping
|
|
SYMBOL: value-literals
|
|
|
|
! Current value --> interval mapping
|
|
SYMBOL: value-intervals
|
|
|
|
! Current value --> class mapping
|
|
SYMBOL: value-classes
|
|
|
|
: value-interval* ( value -- interval/f )
|
|
value-intervals get at ;
|
|
|
|
: set-value-interval* ( interval value -- )
|
|
value-intervals get set-at ;
|
|
|
|
: intersect-value-interval ( interval value -- )
|
|
[ value-interval* interval-intersect ] keep
|
|
set-value-interval* ;
|
|
|
|
M: interval-constraint apply-constraint
|
|
[ interval>> ] [ value>> ] bi intersect-value-interval ;
|
|
|
|
: set-class-interval ( class value -- )
|
|
over class? [
|
|
>r "interval" word-prop r> over
|
|
[ set-value-interval* ] [ 2drop ] if
|
|
] [ 2drop ] if ;
|
|
|
|
: value-class* ( value -- class )
|
|
value-classes get at object or ;
|
|
|
|
: set-value-class* ( class value -- )
|
|
over [
|
|
dup value-intervals get at [
|
|
2dup set-class-interval
|
|
] unless
|
|
2dup <class-constraint> assume
|
|
] when
|
|
value-classes get set-at ;
|
|
|
|
: intersect-value-class ( class value -- )
|
|
[ value-class* class-and ] keep set-value-class* ;
|
|
|
|
M: class-constraint apply-constraint
|
|
[ class>> ] [ value>> ] bi intersect-value-class ;
|
|
|
|
: literal-interval ( value -- interval/f )
|
|
dup real? [ [a,a] ] [ drop f ] if ;
|
|
|
|
: set-value-literal* ( literal value -- )
|
|
{
|
|
[ >r class r> set-value-class* ]
|
|
[ >r literal-interval r> set-value-interval* ]
|
|
[ <literal-constraint> assume ]
|
|
[ value-literals get set-at ]
|
|
} 2cleave ;
|
|
|
|
M: literal-constraint apply-constraint
|
|
[ literal>> ] [ value>> ] bi set-value-literal* ;
|
|
|
|
! For conditionals, an assoc of child node # --> constraint
|
|
GENERIC: child-constraints ( node -- seq )
|
|
|
|
GENERIC: infer-classes-before ( node -- )
|
|
|
|
GENERIC: infer-classes-around ( node -- )
|
|
|
|
M: node infer-classes-before drop ;
|
|
|
|
M: node child-constraints
|
|
children>> length
|
|
dup zero? [ drop f ] [ f <repetition> ] if ;
|
|
|
|
: value-literal* ( value -- obj ? )
|
|
value-literals get at* ;
|
|
|
|
M: literal-constraint constraint-satisfied?
|
|
dup value>> value-literal*
|
|
[ swap literal>> eql? ] [ 2drop f ] if ;
|
|
|
|
M: class-constraint constraint-satisfied?
|
|
[ value>> value-class* ] [ class>> ] bi class<= ;
|
|
|
|
M: pair apply-constraint
|
|
first2 2dup constraints get set-at
|
|
constraint-satisfied? [ apply-constraint ] [ drop ] if ;
|
|
|
|
M: pair constraint-satisfied?
|
|
first constraint-satisfied? ;
|
|
|
|
: valid-keys ( seq assoc -- newassoc )
|
|
extract-keys [ nip ] assoc-filter f assoc-like ;
|
|
|
|
: annotate-node ( node -- )
|
|
#! Annotate the node with the currently-inferred set of
|
|
#! value classes.
|
|
dup node-values {
|
|
[ value-intervals get valid-keys >>intervals ]
|
|
[ value-classes get valid-keys >>classes ]
|
|
[ value-literals get valid-keys >>literals ]
|
|
[ 2drop ]
|
|
} cleave ;
|
|
|
|
: intersect-classes ( classes values -- )
|
|
[ intersect-value-class ] 2each ;
|
|
|
|
: intersect-intervals ( intervals values -- )
|
|
[ intersect-value-interval ] 2each ;
|
|
|
|
: predicate-constraints ( class #call -- )
|
|
[
|
|
! If word outputs true, input is an instance of class
|
|
[
|
|
0 `input class,
|
|
\ f class-not 0 `output class,
|
|
] set-constraints
|
|
] [
|
|
! If word outputs false, input is not an instance of class
|
|
[
|
|
class-not 0 `input class,
|
|
\ f 0 `output class,
|
|
] set-constraints
|
|
] 2bi ;
|
|
|
|
: compute-constraints ( #call -- )
|
|
dup param>> "constraints" word-prop [
|
|
call
|
|
] [
|
|
dup param>> "predicating" word-prop dup
|
|
[ swap predicate-constraints ] [ 2drop ] if
|
|
] if* ;
|
|
|
|
: compute-output-classes ( node word -- classes intervals )
|
|
dup param>> "output-classes" word-prop
|
|
dup [ call ] [ 2drop f f ] if ;
|
|
|
|
: output-classes ( node -- classes intervals )
|
|
dup compute-output-classes >r
|
|
[ ] [ param>> "default-output-classes" word-prop ] ?if
|
|
r> ;
|
|
|
|
M: #call infer-classes-before
|
|
[ compute-constraints ] keep
|
|
[ output-classes ] [ out-d>> ] bi
|
|
tuck [ intersect-classes ] [ intersect-intervals ] 2bi* ;
|
|
|
|
M: #push infer-classes-before
|
|
out-d>> [ [ value-literal ] keep set-value-literal* ] each ;
|
|
|
|
M: #if child-constraints
|
|
[
|
|
\ f class-not 0 `input class,
|
|
f 0 `input literal,
|
|
] make-constraints ;
|
|
|
|
M: #dispatch child-constraints
|
|
dup [
|
|
children>> length [ 0 `input literal, ] each
|
|
] make-constraints ;
|
|
|
|
M: #declare infer-classes-before
|
|
[ param>> ] [ in-d>> ] bi
|
|
[ intersect-value-class ] 2each ;
|
|
|
|
DEFER: (infer-classes)
|
|
|
|
: infer-children ( node -- )
|
|
[ children>> ] [ child-constraints ] bi [
|
|
[
|
|
value-classes [ clone ] change
|
|
value-literals [ clone ] change
|
|
value-intervals [ clone ] change
|
|
constraints [ clone ] change
|
|
apply-constraint
|
|
(infer-classes)
|
|
] with-scope
|
|
] 2each ;
|
|
|
|
: pad-all ( seqs elt -- seq )
|
|
>r dup [ length ] map supremum r> [ pad-left ] 2curry map ;
|
|
|
|
: (merge-classes) ( nodes -- seq )
|
|
dup length 1 = [
|
|
first node-input-classes
|
|
] [
|
|
[ node-input-classes ] map null pad-all flip
|
|
[ null [ class-or ] reduce ] map
|
|
] if ;
|
|
|
|
: set-classes ( seq node -- )
|
|
out-d>> [ set-value-class* ] 2reverse-each ;
|
|
|
|
: merge-classes ( nodes node -- )
|
|
>r (merge-classes) r> set-classes ;
|
|
|
|
: set-intervals ( seq node -- )
|
|
out-d>> [ set-value-interval* ] 2reverse-each ;
|
|
|
|
: merge-intervals ( nodes node -- )
|
|
>r
|
|
[ node-input-intervals ] map f pad-all flip
|
|
[ dup first [ interval-union ] reduce ] map
|
|
r> set-intervals ;
|
|
|
|
: annotate-merge ( nodes #merge/#entry -- )
|
|
[ merge-classes ] [ merge-intervals ] 2bi ;
|
|
|
|
: merge-children ( node -- )
|
|
dup node-successor dup #merge? [
|
|
swap active-children dup empty?
|
|
[ 2drop ] [ swap annotate-merge ] if
|
|
] [ 2drop ] if ;
|
|
|
|
: classes= ( inferred current -- ? )
|
|
2dup min-length [ tail* ] curry bi@ sequence= ;
|
|
|
|
SYMBOL: fixed-point?
|
|
|
|
SYMBOL: nested-labels
|
|
|
|
: annotate-entry ( nodes #label -- )
|
|
>r (merge-classes) r> node-child
|
|
2dup node-output-classes classes=
|
|
[ 2drop ] [ set-classes fixed-point? off ] if ;
|
|
|
|
: init-recursive-calls ( #label -- )
|
|
#! We set recursive calls to output the empty type, then
|
|
#! repeat inference until a fixed point is reached.
|
|
#! Hopefully, our type functions are monotonic so this
|
|
#! will always converge.
|
|
returns>> [ dup in-d>> [ null ] { } map>assoc >>classes drop ] each ;
|
|
|
|
M: #label infer-classes-before ( #label -- )
|
|
[ init-recursive-calls ]
|
|
[ [ 1array ] keep annotate-entry ] bi ;
|
|
|
|
: infer-label-loop ( #label -- )
|
|
fixed-point? on
|
|
dup node-child (infer-classes)
|
|
dup [ calls>> ] [ suffix ] [ annotate-entry ] tri
|
|
fixed-point? get [ drop ] [ infer-label-loop ] if ;
|
|
|
|
M: #label infer-classes-around ( #label -- )
|
|
#! Now merge the types at every recursion point with the
|
|
#! entry types.
|
|
[
|
|
{
|
|
[ nested-labels get push ]
|
|
[ annotate-node ]
|
|
[ infer-classes-before ]
|
|
[ infer-label-loop ]
|
|
[ drop nested-labels get pop* ]
|
|
} cleave
|
|
] with-scope ;
|
|
|
|
: find-label ( param -- #label )
|
|
param>> nested-labels get [ param>> eq? ] with find nip ;
|
|
|
|
M: #call-label infer-classes-before ( #call-label -- )
|
|
[ find-label returns>> (merge-classes) ] [ out-d>> ] bi
|
|
[ set-value-class* ] 2each ;
|
|
|
|
M: #return infer-classes-around
|
|
nested-labels get length 0 > [
|
|
dup param>> nested-labels get peek param>> eq? [
|
|
[ ] [ node-input-classes ] [ in-d>> [ value-class* ] map ] tri
|
|
classes= not [
|
|
fixed-point? off
|
|
[ in-d>> value-classes get valid-keys ] keep
|
|
set-node-classes
|
|
] [ drop ] if
|
|
] [ call-next-method ] if
|
|
] [ call-next-method ] if ;
|
|
|
|
M: object infer-classes-around
|
|
{
|
|
[ infer-classes-before ]
|
|
[ annotate-node ]
|
|
[ infer-children ]
|
|
[ merge-children ]
|
|
} cleave ;
|
|
|
|
: (infer-classes) ( node -- )
|
|
[
|
|
[ infer-classes-around ]
|
|
[ node-successor ] bi
|
|
(infer-classes)
|
|
] when* ;
|
|
|
|
: infer-classes-with ( node classes literals intervals -- )
|
|
[
|
|
V{ } clone nested-labels set
|
|
H{ } assoc-like value-intervals set
|
|
H{ } assoc-like value-literals set
|
|
H{ } assoc-like value-classes set
|
|
H{ } clone constraints set
|
|
(infer-classes)
|
|
] with-scope ;
|
|
|
|
: infer-classes ( node -- node )
|
|
dup f f f infer-classes-with ;
|
|
|
|
: infer-classes/node ( node existing -- )
|
|
#! Infer classes, using the existing node's class info as a
|
|
#! starting point.
|
|
[ classes>> ] [ literals>> ] [ intervals>> ] tri
|
|
infer-classes-with ;
|