factor/core/inference/class/class.factor

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 ;