Inference cleanup and documentation for errors
parent
b2d68abd62
commit
72efe34950
|
@ -1,7 +1,5 @@
|
||||||
+ 0.84:
|
+ 0.84:
|
||||||
|
|
||||||
- document: parse-hook no-parse-hook
|
|
||||||
- document inference errors
|
|
||||||
- update docs for declared effects
|
- update docs for declared effects
|
||||||
- better doc for accumulate, link from tree
|
- better doc for accumulate, link from tree
|
||||||
- RT_WORD should refer to XTs not word objects.
|
- RT_WORD should refer to XTs not word objects.
|
||||||
|
@ -97,6 +95,8 @@
|
||||||
|
|
||||||
+ compiler/ffi:
|
+ compiler/ffi:
|
||||||
|
|
||||||
|
- [ r> ] infer should throw an inference error
|
||||||
|
- better way of dealing with compiler errors
|
||||||
- compiler tests are not as reliable now because of try-compile usage
|
- compiler tests are not as reliable now because of try-compile usage
|
||||||
- we can just do [ t ] [ \ foo compiled? ] unit-test
|
- we can just do [ t ] [ \ foo compiled? ] unit-test
|
||||||
- [ [ dup call ] dup call ] infer hangs
|
- [ [ dup call ] dup call ] infer hangs
|
||||||
|
|
|
@ -78,7 +78,6 @@ $terpri
|
||||||
"The first declaration specifies the time when a word runs. It affects both interpreted and compiled definitions."
|
"The first declaration specifies the time when a word runs. It affects both interpreted and compiled definitions."
|
||||||
{ $subsection POSTPONE: parsing }
|
{ $subsection POSTPONE: parsing }
|
||||||
"The remaining declarations only affect compiled definitions. They do not change evaluation semantics of a word, but instead declare that the word follows a certain contract, and thus may be compiled differently."
|
"The remaining declarations only affect compiled definitions. They do not change evaluation semantics of a word, but instead declare that the word follows a certain contract, and thus may be compiled differently."
|
||||||
$terpri
|
|
||||||
{ $warning "If a generic word is declared " { $link POSTPONE: foldable } ", all methods must satisfy the contract, otherwise unpredicable behavior will occur." }
|
{ $warning "If a generic word is declared " { $link POSTPONE: foldable } ", all methods must satisfy the contract, otherwise unpredicable behavior will occur." }
|
||||||
{ $subsection POSTPONE: inline }
|
{ $subsection POSTPONE: inline }
|
||||||
{ $subsection POSTPONE: foldable } ;
|
{ $subsection POSTPONE: foldable } ;
|
||||||
|
|
|
@ -253,7 +253,12 @@ sequences vectors words ;
|
||||||
"/library/compiler/alien/malloc.facts"
|
"/library/compiler/alien/malloc.facts"
|
||||||
"/library/compiler/alien/structs.facts"
|
"/library/compiler/alien/structs.facts"
|
||||||
"/library/compiler/alien/syntax.facts"
|
"/library/compiler/alien/syntax.facts"
|
||||||
|
"/library/compiler/inference/branches.facts"
|
||||||
|
"/library/compiler/inference/dataflow.facts"
|
||||||
"/library/compiler/inference/inference.facts"
|
"/library/compiler/inference/inference.facts"
|
||||||
|
"/library/compiler/inference/shuffle.facts"
|
||||||
|
"/library/compiler/inference/stack.facts"
|
||||||
|
"/library/compiler/inference/words.facts"
|
||||||
"/library/compiler/compiler.facts"
|
"/library/compiler/compiler.facts"
|
||||||
"/library/generic/early-generic.facts"
|
"/library/generic/early-generic.facts"
|
||||||
"/library/generic/classes.facts"
|
"/library/generic/classes.facts"
|
||||||
|
|
|
@ -4,15 +4,11 @@ IN: inference
|
||||||
USING: arrays errors generic hashtables interpreter kernel math
|
USING: arrays errors generic hashtables interpreter kernel math
|
||||||
namespaces parser prettyprint sequences strings vectors words ;
|
namespaces parser prettyprint sequences strings vectors words ;
|
||||||
|
|
||||||
: unify-lengths ( seq -- seq )
|
: unify-lengths ( seq -- newseq )
|
||||||
#! Pad all vectors to the same length. If one vector is
|
|
||||||
#! shorter, pad it with unknown results at the bottom.
|
|
||||||
dup [ length ] map supremum
|
dup [ length ] map supremum
|
||||||
swap [ add-inputs nip ] map-with ;
|
swap [ add-inputs nip ] map-with ;
|
||||||
|
|
||||||
: unify-values ( seq -- value )
|
: unify-values ( seq -- value )
|
||||||
#! If all values in list are equal, return the value.
|
|
||||||
#! Otherwise, unify.
|
|
||||||
dup all-eq? [ first ] [ drop <computed> ] if ;
|
dup all-eq? [ first ] [ drop <computed> ] if ;
|
||||||
|
|
||||||
: unify-stacks ( seq -- stack ) flip [ unify-values ] map ;
|
: unify-stacks ( seq -- stack ) flip [ unify-values ] map ;
|
||||||
|
@ -21,9 +17,10 @@ namespaces parser prettyprint sequences strings vectors words ;
|
||||||
[ dup [ length - ] [ 2drop f ] if ] 2map
|
[ dup [ length - ] [ 2drop f ] if ] 2map
|
||||||
[ ] subset all-equal? ;
|
[ ] subset all-equal? ;
|
||||||
|
|
||||||
: unbalanced-branches ( in out -- )
|
TUPLE: unbalanced-branches-error in out ;
|
||||||
[ swap unparse " " rot length unparse append3 ] 2map
|
|
||||||
"Unbalanced branches:" add* "\n" join inference-error ;
|
: unbalanced-branches-error ( in out -- * )
|
||||||
|
<unbalanced-branches-error> inference-error ;
|
||||||
|
|
||||||
: unify-inputs ( max-d-in d-in meta-d -- meta-d )
|
: unify-inputs ( max-d-in d-in meta-d -- meta-d )
|
||||||
dup [
|
dup [
|
||||||
|
@ -32,7 +29,7 @@ namespaces parser prettyprint sequences strings vectors words ;
|
||||||
2nip
|
2nip
|
||||||
] if ;
|
] if ;
|
||||||
|
|
||||||
: unify-effect ( in out -- in out )
|
: unify-effect ( in out -- newin newout )
|
||||||
#! in is a sequence of integers, out is a sequence of
|
#! in is a sequence of integers, out is a sequence of
|
||||||
#! stacks.
|
#! stacks.
|
||||||
2dup balanced? [
|
2dup balanced? [
|
||||||
|
@ -84,8 +81,5 @@ namespaces parser prettyprint sequences strings vectors words ;
|
||||||
[ infer-branch ] map dup unify-effects unify-dataflow ;
|
[ infer-branch ] map dup unify-effects unify-dataflow ;
|
||||||
|
|
||||||
: infer-branches ( branches node -- )
|
: infer-branches ( branches node -- )
|
||||||
#! Recursive stack effect inference is done here. If one of
|
|
||||||
#! the branches has an undecidable stack effect, we set the
|
|
||||||
#! base case to this stack effect and try again.
|
|
||||||
[ >r (infer-branches) r> set-node-children ] keep
|
[ >r (infer-branches) r> set-node-children ] keep
|
||||||
node, #merge node, ;
|
node, #merge node, ;
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
IN: inference
|
||||||
|
USING: help interpreter kernel kernel-internals ;
|
||||||
|
|
||||||
|
HELP: unify-lengths
|
||||||
|
{ $values { "seq" "a sequence" } { "newseq" "a new sequence" } }
|
||||||
|
{ $description "Pads sequences in " { $snippet "seq" } " with computed value placeholders to ensure they are all the same length." } ;
|
||||||
|
|
||||||
|
HELP: unify-values
|
||||||
|
{ $values { "seq" "a sequence" } { "value" "an object" } }
|
||||||
|
{ $description "If all values in the sequence are equal, outputs the value, otherwise outputs a computed value placeholder." } ;
|
||||||
|
|
||||||
|
HELP: unbalanced-branches-error
|
||||||
|
{ $values { "in" "a sequence of integers" } { "out" "a sequence of integers" } }
|
||||||
|
{ $description "Throws an " { $link unbalanced-branches-error } "." }
|
||||||
|
{ $error-description "Thrown when inference encounters an " { $link if } ", " { $link dispatch } " or " { $link cond } " where the branches do not all exit with the same stack height." }
|
||||||
|
{ $notes "Conditionals with variable stack effects are considered to be bad style and should be avoided since they do not compile."
|
||||||
|
$terpri
|
||||||
|
"If this error comes up when inferring the stack effect of a recursive word, check the word's stack effect declaration; it might be wrong." } ;
|
||||||
|
|
||||||
|
HELP: unify-effect
|
||||||
|
{ $values { "in" "a sequence of integers" } { "out" "a sequence of stacks" } { "newin" "a sequence of integers" } { "newout" "a sequence of stacks" } }
|
||||||
|
{ $description "Unifies the stack effects of a number of branches, and outputs new values for " { $link d-in } " and " { $link meta-d } "." } ;
|
|
@ -4,10 +4,6 @@ IN: inference
|
||||||
USING: arrays generic hashtables interpreter kernel math
|
USING: arrays generic hashtables interpreter kernel math
|
||||||
namespaces parser sequences words ;
|
namespaces parser sequences words ;
|
||||||
|
|
||||||
! The dataflow IR is the first of the two intermediate
|
|
||||||
! representations used by Factor. It annotates concatenative
|
|
||||||
! code with stack flow information and types.
|
|
||||||
|
|
||||||
TUPLE: node param shuffle
|
TUPLE: node param shuffle
|
||||||
classes literals history
|
classes literals history
|
||||||
successor children ;
|
successor children ;
|
||||||
|
@ -33,10 +29,10 @@ M: node equal? eq? ;
|
||||||
: out-node >r f { } r> { } { } ;
|
: out-node >r f { } r> { } { } ;
|
||||||
: meta-d-node meta-d get clone in-node ;
|
: meta-d-node meta-d get clone in-node ;
|
||||||
|
|
||||||
: d-tail ( n -- list )
|
: d-tail ( n -- seq )
|
||||||
dup zero? [ drop f ] [ meta-d get swap tail* ] if ;
|
dup zero? [ drop f ] [ meta-d get swap tail* ] if ;
|
||||||
|
|
||||||
: r-tail ( n -- list )
|
: r-tail ( n -- seq )
|
||||||
dup zero? [ drop f ] [ meta-r get swap tail* ] if ;
|
dup zero? [ drop f ] [ meta-r get swap tail* ] if ;
|
||||||
|
|
||||||
: node-child node-children first ;
|
: node-child node-children first ;
|
||||||
|
@ -74,8 +70,6 @@ C: #values make-node ;
|
||||||
TUPLE: #return ;
|
TUPLE: #return ;
|
||||||
C: #return make-node ;
|
C: #return make-node ;
|
||||||
: #return ( label -- node )
|
: #return ( label -- node )
|
||||||
#! The parameter is the label we are returning from, or if
|
|
||||||
#! f, this is a top-level return.
|
|
||||||
meta-d-node <#return> [ set-node-param ] keep ;
|
meta-d-node <#return> [ set-node-param ] keep ;
|
||||||
|
|
||||||
TUPLE: #if ;
|
TUPLE: #if ;
|
||||||
|
@ -108,16 +102,13 @@ C: #declare make-node ;
|
||||||
>r r-tail r> set-node-out-r
|
>r r-tail r> set-node-out-r
|
||||||
>r d-tail r> set-node-out-d ;
|
>r d-tail r> set-node-out-d ;
|
||||||
|
|
||||||
! Variable holding dataflow graph being built.
|
|
||||||
SYMBOL: dataflow-graph
|
SYMBOL: dataflow-graph
|
||||||
! The most recently added node.
|
|
||||||
SYMBOL: current-node
|
SYMBOL: current-node
|
||||||
|
|
||||||
: node, ( node -- )
|
: node, ( node -- )
|
||||||
dataflow-graph get [
|
dataflow-graph get [
|
||||||
dup current-node [ set-node-successor ] change
|
dup current-node [ set-node-successor ] change
|
||||||
] [
|
] [
|
||||||
! first node
|
|
||||||
dup dataflow-graph set current-node set
|
dup dataflow-graph set current-node set
|
||||||
] if ;
|
] if ;
|
||||||
|
|
||||||
|
@ -177,8 +168,6 @@ SYMBOL: current-node
|
||||||
swap [ with rot ] all-nodes? 2nip ; inline
|
swap [ with rot ] all-nodes? 2nip ; inline
|
||||||
|
|
||||||
: remember-node ( word node -- )
|
: remember-node ( word node -- )
|
||||||
#! Annotate each node with the fact it was inlined from
|
|
||||||
#! 'word'.
|
|
||||||
[
|
[
|
||||||
dup #call?
|
dup #call?
|
||||||
[ [ node-history ?push ] keep set-node-history ]
|
[ [ node-history ?push ] keep set-node-history ]
|
||||||
|
@ -265,7 +254,6 @@ DEFER: (map-nodes)
|
||||||
] each-node 2drop ;
|
] each-node 2drop ;
|
||||||
|
|
||||||
: subst-values ( new old node -- )
|
: subst-values ( new old node -- )
|
||||||
#! Mutates nodes.
|
|
||||||
node-stack get 1 head-slice* swap add
|
node-stack get 1 head-slice* swap add
|
||||||
[ >r 2dup r> node-successor (subst-values) ] each 2drop ;
|
[ >r 2dup r> node-successor (subst-values) ] each 2drop ;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
IN: inference
|
||||||
|
USING: help ;
|
||||||
|
|
||||||
|
HELP: #return
|
||||||
|
{ $values { "label" "a word or " { $link f } } }
|
||||||
|
{ $description "Creates a node which returns from a nested label, or if " { $snippet "label" } " is " { $link f } ", the top-level word being compiled." } ;
|
||||||
|
|
||||||
|
HELP: dataflow-graph
|
||||||
|
{ $var-description "In the dynamic extent of " { $link infer } " and " { $link dataflow } ", holds the first node of the dataflow graph being constructed." } ;
|
||||||
|
|
||||||
|
HELP: current-node
|
||||||
|
{ $var-description "In the dynamic extent of " { $link infer } " and " { $link dataflow } ", holds the most recently added node of the dataflow graph being constructed." } ;
|
||||||
|
|
||||||
|
HELP: remember-node
|
||||||
|
{ $values { "word" "a word" } { "node" "a dataflow node" } }
|
||||||
|
{ $description "Annotates all nodes starting from " { $snippet "node" } " with the fact that they were inlined from " { $snippet "word" } ". This prevents infinite loops when the optimizer inlines words." } ;
|
|
@ -5,23 +5,16 @@ USING: arrays errors generic inspector interpreter io kernel
|
||||||
math namespaces parser prettyprint sequences strings
|
math namespaces parser prettyprint sequences strings
|
||||||
vectors words ;
|
vectors words ;
|
||||||
|
|
||||||
TUPLE: inference-error message rstate data-stack call-stack ;
|
TUPLE: inference-error message rstate ;
|
||||||
|
|
||||||
: inference-error ( msg -- * )
|
: inference-error ( msg -- * )
|
||||||
recursive-state get meta-d get meta-r get
|
recursive-state get <inference-error> throw ;
|
||||||
<inference-error> throw ;
|
|
||||||
|
TUPLE: literal-expected ;
|
||||||
|
|
||||||
M: object value-literal
|
M: object value-literal
|
||||||
"A literal value was expected where a computed value was found" inference-error ;
|
<literal-expected> inference-error ;
|
||||||
|
|
||||||
! Word properties that affect inference:
|
|
||||||
! - infer-effect -- must be set. controls number of inputs
|
|
||||||
! expected, and number of outputs produced.
|
|
||||||
! - infer - quotation with custom inference behavior; 'if' uses
|
|
||||||
! this. Word is passed on the stack.
|
|
||||||
|
|
||||||
! Number of values we had to add to the datastack. Ie, the
|
|
||||||
! inputs.
|
|
||||||
SYMBOL: d-in
|
SYMBOL: d-in
|
||||||
|
|
||||||
: pop-literal ( -- rstate obj )
|
: pop-literal ( -- rstate obj )
|
||||||
|
@ -41,8 +34,6 @@ SYMBOL: d-in
|
||||||
: short-effect ( -- pair )
|
: short-effect ( -- pair )
|
||||||
d-in get meta-d get length 2array ;
|
d-in get meta-d get length 2array ;
|
||||||
|
|
||||||
! Does this control flow path throw an exception, therefore its
|
|
||||||
! stack height is irrelevant and the branch will always unify?
|
|
||||||
SYMBOL: terminated?
|
SYMBOL: terminated?
|
||||||
|
|
||||||
: current-effect ( -- effect )
|
: current-effect ( -- effect )
|
||||||
|
@ -63,8 +54,6 @@ SYMBOL: recorded
|
||||||
GENERIC: apply-object
|
GENERIC: apply-object
|
||||||
|
|
||||||
: apply-literal ( obj -- )
|
: apply-literal ( obj -- )
|
||||||
#! Literals are annotated with the current recursive
|
|
||||||
#! state.
|
|
||||||
<value> push-d #push node, ;
|
<value> push-d #push node, ;
|
||||||
|
|
||||||
M: object apply-object apply-literal ;
|
M: object apply-object apply-literal ;
|
||||||
|
@ -72,7 +61,6 @@ M: object apply-object apply-literal ;
|
||||||
M: wrapper apply-object wrapped apply-literal ;
|
M: wrapper apply-object wrapped apply-literal ;
|
||||||
|
|
||||||
: terminate ( -- )
|
: terminate ( -- )
|
||||||
#! Ignore this branch's stack effect.
|
|
||||||
terminated? on #terminate node, ;
|
terminated? on #terminate node, ;
|
||||||
|
|
||||||
GENERIC: infer-quot ( quot -- )
|
GENERIC: infer-quot ( quot -- )
|
||||||
|
@ -80,20 +68,17 @@ GENERIC: infer-quot ( quot -- )
|
||||||
M: f infer-quot drop ;
|
M: f infer-quot drop ;
|
||||||
|
|
||||||
M: quotation infer-quot
|
M: quotation infer-quot
|
||||||
#! Recursive calls to this word are made for nested
|
|
||||||
#! quotations.
|
|
||||||
[ apply-object terminated? get not ] all? drop ;
|
[ apply-object terminated? get not ] all? drop ;
|
||||||
|
|
||||||
: infer-quot-value ( rstate quot -- )
|
: infer-quot-value ( rstate quot -- )
|
||||||
recursive-state get >r swap recursive-state set
|
recursive-state get >r swap recursive-state set
|
||||||
infer-quot r> recursive-state set ;
|
infer-quot r> recursive-state set ;
|
||||||
|
|
||||||
|
TUPLE: check-return ;
|
||||||
|
|
||||||
: check-return ( -- )
|
: check-return ( -- )
|
||||||
#! Raise an error if word leaves values on return stack.
|
|
||||||
meta-r get empty? [
|
meta-r get empty? [
|
||||||
"Word leaves " meta-r get length number>string
|
<check-return> inference-error
|
||||||
" element(s) on retain stack. Check >r/r> usage." append3
|
|
||||||
inference-error
|
|
||||||
] unless ;
|
] unless ;
|
||||||
|
|
||||||
: undo-infer ( -- )
|
: undo-infer ( -- )
|
||||||
|
@ -116,16 +101,13 @@ M: quotation infer-quot
|
||||||
] with-scope ;
|
] with-scope ;
|
||||||
|
|
||||||
: infer ( quot -- effect )
|
: infer ( quot -- effect )
|
||||||
#! Stack effect of a quotation.
|
|
||||||
[ infer-quot short-effect ] with-infer ;
|
[ infer-quot short-effect ] with-infer ;
|
||||||
|
|
||||||
: (dataflow) ( quot -- dataflow )
|
: (dataflow) ( quot -- dataflow )
|
||||||
infer-quot f #return node, dataflow-graph get ;
|
infer-quot f #return node, dataflow-graph get ;
|
||||||
|
|
||||||
: dataflow ( quot -- dataflow )
|
: dataflow ( quot -- dataflow )
|
||||||
#! Data flow of a quotation.
|
|
||||||
[ (dataflow) ] with-infer ;
|
[ (dataflow) ] with-infer ;
|
||||||
|
|
||||||
: dataflow-with ( quot stack -- effect )
|
: dataflow-with ( quot stack -- effect )
|
||||||
#! Infer starting from a stack of values.
|
|
||||||
[ meta-d set (dataflow) ] with-infer ;
|
[ meta-d set (dataflow) ] with-infer ;
|
||||||
|
|
|
@ -1,20 +1,49 @@
|
||||||
IN: inference
|
IN: inference
|
||||||
USING: help kernel ;
|
USING: compiler help kernel sequences ;
|
||||||
|
|
||||||
HELP: inference-error
|
HELP: inference-error
|
||||||
{ $values { "msg" "an object" } }
|
{ $values { "msg" "an object" } }
|
||||||
{ $description "Throws an " { $link inference-error } "." }
|
{ $description "Throws an " { $link inference-error } "." }
|
||||||
{ $error-description
|
{ $error-description
|
||||||
"Thrown by " { $link infer } " when the stack effect of a quotation cannot be inferred. There are several possible reasons that this can occur:"
|
"Thrown by " { $link infer } ", " { $link dataflow } " and " { $link compile } " when the stack effect of a quotation cannot be inferred."
|
||||||
|
$terpri
|
||||||
|
"This error always delegates to one of the following classes of errors, which indicate the specific issue preventing a stack effect from being inferred:"
|
||||||
{ $list
|
{ $list
|
||||||
{ "The quotation applies " { $link call } " or " { $link if } " to quotation values which are not literals; thus the potential stack effect is arbitrary" }
|
{ $link no-effect }
|
||||||
"The quotation involves conditionals where the branches have incompatible stack effects"
|
{ $link literal-expected }
|
||||||
"The quotation calls a recursive word with no base case"
|
{ $link check-return }
|
||||||
|
{ $link unbalanced-branches-error }
|
||||||
|
{ $link effect-error }
|
||||||
|
{ $link recursive-declare-error }
|
||||||
}
|
}
|
||||||
"Words without a static stack effect cannot be compiled, but will still run in the interpreter."
|
|
||||||
} ;
|
} ;
|
||||||
|
|
||||||
|
HELP: literal-expected
|
||||||
|
{ $error-description "Thrown when inference encounters a " { $link call } " or " { $link if } " being applied to a value which is not known to be a literal. Such a form can have an arbitrary stack effect, and does not compile." }
|
||||||
|
{ $notes "This error will be thrown when compiling any combinator, such as " { $link each } ". However, words calling combinators can compile of the combinator is declared " { $link inline } " and the quotation being passed in is a literal." } ;
|
||||||
|
|
||||||
|
HELP: d-in
|
||||||
|
{ $var-description "During inference, holds the number of inputs which the quotation has been inferred to require so far." } ;
|
||||||
|
|
||||||
|
HELP: terminated?
|
||||||
|
{ $var-description "During inference, a flag set to " { $link t } " if the current control flow path unconditionally throws an error." } ;
|
||||||
|
|
||||||
|
HELP: check-return
|
||||||
|
{ $error-description "Thrown if inference notices a quotation leaving behind elements on the retain stack." }
|
||||||
|
{ $notes "Usually this error indicates a coding mistake; check that usages of " { $link >r } " and " { $link r> } " are balanced in this case. Writing code which intentionally does this is considered bad style." } ;
|
||||||
|
|
||||||
HELP: infer
|
HELP: infer
|
||||||
{ $values { "quot" "a quotation" } { "effect" "a pair of integers" } }
|
{ $values { "quot" "a quotation" } { "effect" "a pair of integers" } }
|
||||||
{ $description "Attempts to infer the quotation's stack effect, outputting a pair holding the correct of data stack inputs and outputs for the quotation." }
|
{ $description "Attempts to infer the quotation's stack effect, and outputs a pair holding the correct of data stack inputs and outputs for the quotation." }
|
||||||
{ $errors "Throws an error if stack effect inference fails." } ;
|
{ $errors "Throws an " { $link inference-error } " if stack effect inference fails." } ;
|
||||||
|
|
||||||
|
HELP: dataflow
|
||||||
|
{ $values { "quot" "a quotation" } { "dataflow" "a dataflow node" } }
|
||||||
|
{ $description "Attempts to construct a dataflow graph showing stack flow in the quotation." }
|
||||||
|
{ $notes "This is the first stage of the compiler." }
|
||||||
|
{ $errors "Throws an " { $link inference-error } " if stack effect inference fails." } ;
|
||||||
|
|
||||||
|
HELP: dataflow-with
|
||||||
|
{ $values { "quot" "a quotation" } { "stack" "a vector" } { "dataflow" "a dataflow node" } }
|
||||||
|
{ $description "Attempts to construct a dataflow graph showing stack flow in the quotation, starting with an initial data stack of values." }
|
||||||
|
{ $errors "Throws an " { $link inference-error } " if stack effect inference fails." } ;
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
IN: inference
|
IN: inference
|
||||||
USING: hashtables kernel math namespaces sequences ;
|
USING: hashtables kernel math namespaces sequences ;
|
||||||
|
|
||||||
! Recursive state. An alist, mapping words to labels.
|
|
||||||
SYMBOL: recursive-state
|
SYMBOL: recursive-state
|
||||||
|
|
||||||
: <computed> \ <computed> counter ;
|
: <computed> \ <computed> counter ;
|
||||||
|
@ -46,9 +45,7 @@ TUPLE: shuffle in-d in-r out-d out-r ;
|
||||||
: join-shuffle ( d' r' d r -- d r )
|
: join-shuffle ( d' r' d r -- d r )
|
||||||
swapd append >r append r> ;
|
swapd append >r append r> ;
|
||||||
|
|
||||||
: shuffle ( d r shuffle -- d r )
|
: shuffle ( d r shuffle -- newd newr )
|
||||||
#! d and r lengths must be at least the required length for
|
|
||||||
#! the shuffle.
|
|
||||||
[ split-shuffle ] keep shuffle* join-shuffle ;
|
[ split-shuffle ] keep shuffle* join-shuffle ;
|
||||||
|
|
||||||
M: shuffle clone
|
M: shuffle clone
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
IN: inference
|
||||||
|
USING: help ;
|
||||||
|
|
||||||
|
HELP: recursive-state
|
||||||
|
{ $var-description "During inference, holds an association list mapping words to labels." } ;
|
||||||
|
|
||||||
|
HELP: shuffle
|
||||||
|
{ $values { "d" "a sequence" } { "r" "a sequence" } { "shuffle" "an instance of " { $link shuffle } } { "newd" "a new sequence" } { "newr" "a new sequence" } }
|
||||||
|
{ $description "Applies a stack shuffle pattern to a pair of stacks." }
|
||||||
|
{ $errors "Throws an error if the input stacks contain insufficient elements." } ;
|
|
@ -0,0 +1,7 @@
|
||||||
|
IN: inference
|
||||||
|
USING: help ;
|
||||||
|
|
||||||
|
HELP: shuffle-stacks
|
||||||
|
{ $values { "shuffle" "an instance of " { $link shuffle } } }
|
||||||
|
{ $description "Applies a stack shuffle pattern to the inference stacks." }
|
||||||
|
{ $errors "Throws an error if the stacks contain insufficient elements." } ;
|
|
@ -24,8 +24,6 @@ IN: inference
|
||||||
if ;
|
if ;
|
||||||
|
|
||||||
: consume/produce ( word effect -- )
|
: consume/produce ( word effect -- )
|
||||||
#! Add a node to the dataflow graph that consumes and
|
|
||||||
#! produces a number of values.
|
|
||||||
meta-d get clone >r
|
meta-d get clone >r
|
||||||
swap make-call-node
|
swap make-call-node
|
||||||
over effect-in length over consume-values
|
over effect-in length over consume-values
|
||||||
|
@ -33,10 +31,10 @@ IN: inference
|
||||||
r> over #call-label? [ over set-node-in-d ] [ drop ] if
|
r> over #call-label? [ over set-node-in-d ] [ drop ] if
|
||||||
node, effect-terminated? [ terminate ] when ;
|
node, effect-terminated? [ terminate ] when ;
|
||||||
|
|
||||||
|
TUPLE: no-effect word ;
|
||||||
|
|
||||||
: no-effect ( word -- * )
|
: no-effect ( word -- * )
|
||||||
"Stack effect inference of the word " swap word-name
|
<no-effect> inference-error ;
|
||||||
" was already attempted, and failed" append3
|
|
||||||
inference-error ;
|
|
||||||
|
|
||||||
: nest-node ( -- ) #entry node, ;
|
: nest-node ( -- ) #entry node, ;
|
||||||
|
|
||||||
|
@ -67,32 +65,20 @@ M: #call-label collect-recursion*
|
||||||
tuck node-param eq? [ node-in-d , ] [ drop ] if ;
|
tuck node-param eq? [ node-in-d , ] [ drop ] if ;
|
||||||
|
|
||||||
: collect-recursion ( #label -- seq )
|
: collect-recursion ( #label -- seq )
|
||||||
#! Collect the input stacks of all #call-label nodes that
|
|
||||||
#! call given label.
|
|
||||||
dup node-param swap
|
dup node-param swap
|
||||||
[ [ collect-recursion* ] each-node-with ] { } make ;
|
[ [ collect-recursion* ] each-node-with ] { } make ;
|
||||||
|
|
||||||
: join-values ( node -- )
|
: join-values ( node -- )
|
||||||
#! We have to infer recursive labels twice to determine
|
|
||||||
#! which literals survive the recursion (eg, quotations)
|
|
||||||
#! and which don't (loop indices, etc). The latter cannot
|
|
||||||
#! be folded.
|
|
||||||
collect-recursion meta-d get add unify-lengths unify-stacks
|
collect-recursion meta-d get add unify-lengths unify-stacks
|
||||||
meta-d [ length tail* >vector ] change ;
|
meta-d [ length tail* >vector ] change ;
|
||||||
|
|
||||||
: splice-node ( node -- )
|
: splice-node ( node -- )
|
||||||
#! Labels which do not call themselves are just spliced into
|
|
||||||
#! the IR, and no #label node is added.
|
|
||||||
dup node-successor [
|
dup node-successor [
|
||||||
dup node, penultimate-node f over set-node-successor
|
dup node, penultimate-node f over set-node-successor
|
||||||
dup current-node set
|
dup current-node set
|
||||||
] when drop ;
|
] when drop ;
|
||||||
|
|
||||||
: inline-closure ( word -- )
|
: inline-closure ( word -- )
|
||||||
#! This is not a closure in the lexical scope sense, but a
|
|
||||||
#! closure under recursive value substitution.
|
|
||||||
#! If the block does not call itself, there is no point in
|
|
||||||
#! having the block node in the IR. Just add its contents.
|
|
||||||
dup inline-block over recursive-label? [
|
dup inline-block over recursive-label? [
|
||||||
meta-d get >r
|
meta-d get >r
|
||||||
drop join-values inline-block apply-infer
|
drop join-values inline-block apply-infer
|
||||||
|
@ -109,13 +95,12 @@ M: #call-label collect-recursion*
|
||||||
|
|
||||||
GENERIC: apply-word
|
GENERIC: apply-word
|
||||||
|
|
||||||
M: object apply-word
|
M: object apply-word no-effect ;
|
||||||
#! A primitive with an unknown stack effect.
|
|
||||||
no-effect ;
|
|
||||||
|
|
||||||
TUPLE: effect-error word effect ;
|
TUPLE: effect-error word effect ;
|
||||||
|
|
||||||
: effect-error ( word effect -- * ) <effect-error> throw ;
|
: effect-error ( word effect -- * )
|
||||||
|
<effect-error> inference-error ;
|
||||||
|
|
||||||
: check-effect ( word effect -- )
|
: check-effect ( word effect -- )
|
||||||
over "infer" word-prop [
|
over "infer" word-prop [
|
||||||
|
@ -128,7 +113,6 @@ TUPLE: effect-error word effect ;
|
||||||
] if ;
|
] if ;
|
||||||
|
|
||||||
M: compound apply-word
|
M: compound apply-word
|
||||||
#! Infer a compound word's stack effect.
|
|
||||||
[
|
[
|
||||||
dup infer-compound check-effect
|
dup infer-compound check-effect
|
||||||
] [
|
] [
|
||||||
|
@ -154,21 +138,13 @@ M: word apply-object apply-default ;
|
||||||
|
|
||||||
M: symbol apply-object apply-literal ;
|
M: symbol apply-object apply-literal ;
|
||||||
|
|
||||||
: declared-effect ( word -- effect )
|
TUPLE: recursive-declare-error word ;
|
||||||
dup "declared-effect" word-prop [ ] [
|
|
||||||
"The recursive word " swap word-name
|
|
||||||
" does not declare a stack effect" append3
|
|
||||||
inference-error
|
|
||||||
] ?if ;
|
|
||||||
|
|
||||||
: recursive-effect ( word -- effect )
|
: recursive-effect ( word -- effect )
|
||||||
#! Handle a recursive call, by either applying a previously
|
stack-effect
|
||||||
#! inferred base case, or raising an error. If the recursive
|
[ ] [ <recursive-declare-error> inference-error ] ?if ;
|
||||||
#! call is to a local block, emit a label call node.
|
|
||||||
dup "infer-effect" word-prop [ ] [ declared-effect ] ?if ;
|
|
||||||
|
|
||||||
M: compound apply-object
|
M: compound apply-object
|
||||||
#! Apply the word's stack effect to the inferencer state.
|
|
||||||
dup "inline" word-prop [
|
dup "inline" word-prop [
|
||||||
dup recursive-state get peek first eq? [
|
dup recursive-state get peek first eq? [
|
||||||
dup recursive-effect consume/produce
|
dup recursive-effect consume/produce
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
IN: inference
|
||||||
|
USING: help words ;
|
||||||
|
|
||||||
|
HELP: consume/produce
|
||||||
|
{ $values { "word" "a word" } { "effect" "an instance of " { $link effect } } }
|
||||||
|
{ $description "Adds a node to the dataflow graph that calls " { $snippet "word" } " with a stack effect of " { $snippet "effect" } "." } ;
|
||||||
|
|
||||||
|
HELP: no-effect
|
||||||
|
{ $values { "word" "a word" } }
|
||||||
|
{ $description "Throws a " { $link no-effect } " error." }
|
||||||
|
{ $error-description "Thrown when inference encounters a call to a word which is already known not to have a static stack effect, due to a prior inference attempt failing." } ;
|
||||||
|
|
||||||
|
HELP: collect-recursion
|
||||||
|
{ $values { "#label" "a " { $link #label } " node" } { "seq" "a new sequence" } }
|
||||||
|
{ $description "Collect the input stacks of all child " { $link #call-label } " nodes that call the given label." } ;
|
||||||
|
|
||||||
|
HELP: inline-closure
|
||||||
|
{ $values { "word" "a word" } }
|
||||||
|
{ $description "Called during inference to infer stack effects of inline words."
|
||||||
|
$terpri
|
||||||
|
"If the inline word is recursive, a new " { $link #label } " node is added to the dataflow graph, and the word has to be inferred twice, to determine which literals survive the recursion (eg, quotations) and which don't (loop indices, etc)."
|
||||||
|
$terpri
|
||||||
|
"If the inline word is not recursive, the resulting nodes are spliced into the dataflow graph, and no " { $link #label } " node is created." } ;
|
||||||
|
|
||||||
|
HELP: effect-error
|
||||||
|
{ $values { "word" "a word" } { "effect" "an instance of " { $link effect } } }
|
||||||
|
{ $description "Throws an " { $link effect-error } "." }
|
||||||
|
{ $error-description "Thrown when a word's inferred stack effect does not match its declared stack effect." } ;
|
||||||
|
|
||||||
|
HELP: recursive-declare-error
|
||||||
|
{ $error-description "Thrown when inference encounters a recursive call to a word lacking a stack effect declaration. Recursive words must declare a stack effect in order to compile. Due to implementation detail, generic words are recursive, and thus the same restriction applies." } ;
|
|
@ -1,4 +1,4 @@
|
||||||
USING: help io jedit parser ;
|
USING: compiler help io jedit parser ;
|
||||||
|
|
||||||
HELP: file-vocabs
|
HELP: file-vocabs
|
||||||
{ $description "Installs the initial the vocabulary search path for parsing a file." } ;
|
{ $description "Installs the initial the vocabulary search path for parsing a file." } ;
|
||||||
|
@ -22,6 +22,14 @@ HELP: eval
|
||||||
{ $description "Parses Factor source code from a string, and calls the resulting quotation. The current vocabulary search path is used." }
|
{ $description "Parses Factor source code from a string, and calls the resulting quotation. The current vocabulary search path is used." }
|
||||||
{ $errors "Throws an error if the input is malformed, or if the quotation throws an error." } ;
|
{ $errors "Throws an error if the input is malformed, or if the quotation throws an error." } ;
|
||||||
|
|
||||||
|
HELP: parse-hook
|
||||||
|
{ $var-description "A quotation called by " { $link parse-stream } " after parsing the input stream. The default value calls " { $link recompile } " to recompile any changed definitions." }
|
||||||
|
{ $see-also no-parse-hook } ;
|
||||||
|
|
||||||
|
HELP: no-parse-hook
|
||||||
|
{ $values { "quot" "a quotation" } }
|
||||||
|
{ $description "Runs the quotation in a new dynamic scope where " { $link parse-hook } " is set to " { $link f } ". This disables the default behavior of recompiling changed definitions after a source file is loaded." } ;
|
||||||
|
|
||||||
HELP: parse-stream
|
HELP: parse-stream
|
||||||
{ $values { "stream" "an input stream" } { "name" "a file name for error reporting" } { "quot" "a new quotation" } }
|
{ $values { "stream" "an input stream" } { "name" "a file name for error reporting" } { "quot" "a new quotation" } }
|
||||||
{ $description "Parses Factor source code read from the stream. The initial vocabulary search path is used." }
|
{ $description "Parses Factor source code read from the stream. The initial vocabulary search path is used." }
|
||||||
|
|
|
@ -309,6 +309,8 @@ DEFER: bar
|
||||||
: bad-bin ( a b -- ) 5 [ 5 bad-bin bad-bin 5 ] [ 2drop ] if ;
|
: bad-bin ( a b -- ) 5 [ 5 bad-bin bad-bin 5 ] [ 2drop ] if ;
|
||||||
[ [ bad-bin ] infer ] unit-test-fails
|
[ [ bad-bin ] infer ] unit-test-fails
|
||||||
|
|
||||||
|
[ t ] [ [ [ r> ] infer ] catch inference-error? ] unit-test
|
||||||
|
|
||||||
! Test some random library words
|
! Test some random library words
|
||||||
|
|
||||||
[ { 1 1 } ] [ [ unit ] infer ] unit-test
|
[ { 1 1 } ] [ [ unit ] infer ] unit-test
|
||||||
|
|
|
@ -168,7 +168,38 @@ M: alien-invoke-error summary
|
||||||
M: assert summary drop "Assertion failed" ;
|
M: assert summary drop "Assertion failed" ;
|
||||||
|
|
||||||
M: inference-error error.
|
M: inference-error error.
|
||||||
"Inference error:" print
|
dup inference-error-message error.
|
||||||
dup inference-error-message print
|
"Nesting: " write
|
||||||
"Recursive state:" print
|
inference-error-rstate [ first ] map . ;
|
||||||
inference-error-rstate describe ;
|
|
||||||
|
M: inference-error error-help drop f ;
|
||||||
|
|
||||||
|
M: unbalanced-branches error.
|
||||||
|
"Unbalanced branches:" print
|
||||||
|
dup unbalanced-branches-out
|
||||||
|
swap unbalanced-branches-in
|
||||||
|
[ pprint bl pprint ] 2map ;
|
||||||
|
|
||||||
|
M: literal-expected summary
|
||||||
|
drop "Literal value expected" ;
|
||||||
|
|
||||||
|
M: retain-leave-error summary
|
||||||
|
drop
|
||||||
|
"Quotation leaves elements behind on retain stack" ;
|
||||||
|
|
||||||
|
M: no-effect error.
|
||||||
|
"The word " write
|
||||||
|
no-effect-word pprint
|
||||||
|
" does not have a stack effect" print ;
|
||||||
|
|
||||||
|
M: recursive-declare-error error.
|
||||||
|
"The recursive word " write
|
||||||
|
recursive-declare-error-word pprint
|
||||||
|
" must declare a stack effect" print ;
|
||||||
|
|
||||||
|
M: effect-error error.
|
||||||
|
"Stack effects of the word " write
|
||||||
|
dup effect-error-word pprint
|
||||||
|
" do not match." print
|
||||||
|
"Declared: " write dup effect-error-word stack-effect .
|
||||||
|
"Inferred: " write effect-error-effect . ;
|
||||||
|
|
Loading…
Reference in New Issue