Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] Multiple dispatch with existing inline cache infrastructure and abstractions for dispatch specifiers #2430

Draft
wants to merge 45 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
530485b
classes.predicate: Allow declaring predicate classes disjoint
timor May 6, 2021
db1c73a
classes.classifications: Add syntax to declare disjoint unions classes
timor May 6, 2021
d38097f
generic.multi: multiple-dispatch using nested inline caches
timor Feb 8, 2021
0d499b6
generic.multi: implement combination interface and syntax
timor Feb 12, 2021
ed1a994
generic.multi: promote lower-arity methods correctly
timor Feb 13, 2021
2395188
generic.multi: correctly generate nested predicate engines
timor Feb 13, 2021
d360b05
generic.multi: generate PIC quotations, wrong version of inlining
timor Feb 13, 2021
4dc8ee0
classes.algebra: revert to promotion semantics for covariant tuples
timor Feb 13, 2021
70c0278
generic.multi: Support inlining
timor Feb 14, 2021
750e7d5
generic.multi: Test ambiguity checking during compilation
timor Feb 14, 2021
0bb9502
generic.multi: generate PICs for nested dispatch
timor Feb 14, 2021
fa7edb8
generic.multi: conditionally enable nested dispatch pic generation
timor Feb 14, 2021
5b9d2a2
extra/generic.multi.benchmark: include version to compare current fea…
timor Feb 14, 2021
e238d0e
generic.multi: remove remnants from earlier attempts
timor Feb 14, 2021
1330349
extra/generic.multi.benchmark: add benchmarks for tuple classes
timor Feb 15, 2021
33e16ab
generic.multi: fix usings, don't use smart combinators
timor Feb 15, 2021
b2f414f
generic.multi: remove broken compile-time lookup
timor Feb 15, 2021
2dab7ea
generic/compiler: Allow method combinations to generate compiler errors
timor Feb 17, 2021
e7b9e71
classes.algebra: Implement distributive semantics for covariant tuple…
timor Feb 17, 2021
6b8bab4
generic.multi: Detect ambiguous multi-methods during compilation.
timor Feb 17, 2021
ba90b1f
generic.multi: Support call-next-method
timor Feb 17, 2021
12b1c99
generic.multi: Some convience syntax for dispatch tuples
timor Feb 17, 2021
fd84e8c
classes.dispatch.*: Factor out dispatch types/covariant tuples...
timor Feb 19, 2021
ffc3cd3
classes.dispatch: Remove unused generic, methods on class
timor Feb 19, 2021
9658210
classes.dispatch: Make predicate generation generic, clean up...
timor Feb 19, 2021
8d68609
generic.multi/classes.dispatch: Fix predicate engines, only use dispa…
timor Feb 19, 2021
e19a4d7
classes.dispatch.eql: Implement eql specializers on new dispatch-type…
timor Feb 19, 2021
4294248
generic.multi: added tests for currently not working eql specializer …
timor Feb 19, 2021
871c94b
classes.dispatch: dispatch<= -> left-dispatch<= + right-dispatch<=, f…
timor Feb 19, 2021
9780b96
generic.multi: fix too optimistic nested dispatch shortcut
timor Feb 22, 2021
f5379c0
generic.multi: add regression test for singleton classes
timor Mar 11, 2021
4882d56
generic.multi: add test for ambiguous CLOS comparison example
timor Mar 13, 2021
a57922e
classes.dispatch.class: Add class specializers and class methods
timor Apr 3, 2021
9069d95
classes.dispatch: dev doc
timor Apr 5, 2021
933213e
generic.multi: Fix dispatching when object was involved
timor Apr 5, 2021
59e9517
classes.dispatch.class: Fix dispatch generation of class methods
timor Apr 6, 2021
db4175d
classes.dispatch.syntax: CM: was not wrapping the specializer correctly
timor Apr 6, 2021
6845db9
generic.multi: Add unit test for CM:
timor Apr 6, 2021
ff73f3b
core syntax: Define ")" as possible delimiter
timor Apr 7, 2021
ccd6f12
classes.dispatch.syntax: D{ } → D( ), prettyprint the specializers
timor Apr 7, 2021
64fb2f4
generic.multi: Add "multi" decorator
timor Apr 7, 2021
150ade3
generic.multi: Test DISJOINT: disambiguation declaration
timor Apr 7, 2021
fb7352e
classes.dispatch: Add word for checking class participation
timor Apr 15, 2021
aa85be1
generic: Correctly forget multi-methods when classes are forgotten
timor Apr 15, 2021
c67ed94
generic.multi: Ditch automatic conversion to multi-generic for now
Apr 18, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 12 additions & 2 deletions basis/compiler/compiler.factor
Expand Up @@ -5,8 +5,8 @@ combinators.short-circuit compiler.cfg compiler.cfg.builder
compiler.cfg.builder.alien compiler.cfg.finalization
compiler.cfg.optimizer compiler.codegen compiler.crossref
compiler.errors compiler.tree.builder compiler.tree.optimizer
compiler.units compiler.utilities continuations definitions fry
generic generic.single io kernel macros make namespaces
compiler.units compiler.utilities continuations definitions
generic generic.single generic.multi io kernel macros make namespaces
sequences sets stack-checker.dependencies stack-checker.errors
stack-checker.inlining vocabs.loader words ;
IN: compiler
Expand Down Expand Up @@ -102,6 +102,8 @@ M: word combinator? inline? ;
{
[ single-generic? ]
[ primitive? ]
[ multi-generic? ]
[ nested-dispatch-engine-word? ]
} 1|| not ;

: contains-breakpoints? ( -- ? )
Expand All @@ -125,12 +127,20 @@ M: word combinator? inline? ;
] with-cfg
] each ;

! NOTE: this uses a mechanism which is normally only used in the code path of
! optimized word in the compiler vocab. The methods will still be compiled. I
! suppose this is more of a warning kind of thing.
: detect-generic-errors ( generic -- )
[ check-generic ]
[ swap <compiler-error> save-compiler-error ] recover ;

: compile-word ( word -- )
! We return early if the word has breakpoints or if it
! failed to infer.
'[
_ {
[ start-compilation ]
[ detect-generic-errors ]
[ frontend ]
[ backend ]
[ finish-compilation ]
Expand Down
25 changes: 20 additions & 5 deletions basis/compiler/tree/propagation/inlining/inlining.factor
@@ -1,11 +1,11 @@
! Copyright (C) 2008, 2009 Slava Pestov.
! See http://factorcode.org/license.txt for BSD license.
USING: accessors arrays assocs classes.algebra combinators
combinators.short-circuit compiler.tree compiler.tree.builder
USING: accessors arrays assocs classes.algebra classes.dispatch.covariant-tuples
combinators combinators.short-circuit compiler.tree compiler.tree.builder
compiler.tree.normalization compiler.tree.propagation.info
compiler.tree.propagation.nodes compiler.tree.recursive generic
generic.math generic.single generic.standard kernel locals math
math.partial-dispatch namespaces quotations sequences words ;
compiler.tree.propagation.nodes compiler.tree.recursive generic generic.math
generic.multi generic.single generic.standard kernel math math.partial-dispatch
namespaces quotations sequences words ;
IN: compiler.tree.propagation.inlining

: splicing-call ( #call word -- nodes )
Expand Down Expand Up @@ -52,6 +52,20 @@ M: callable splicing-nodes splicing-body ;
: inline-standard-method ( #call word -- ? )
dupd inlining-standard-method eliminate-dispatch ;

! FIXME: Almost copy/paste from inlining-standard-method, abstract
! dispatch-classes or something
: inlining-multi-method ( #call word -- class/f method/f )
dup "methods" word-prop assoc-empty? [ 2drop f f ] [
2dup [ in-d>> length ] [ multi-generic-arity ] bi* < [ 2drop f f ] [
[ in-d>> ] [ [ multi-generic-arity ] keep ] bi*
[ tail* [ value-info class>> ] map <covariant-tuple> dup ] dip
method-for-class
] if
] if ;

: inline-multi-method ( #call word -- ? )
dupd inlining-multi-method eliminate-dispatch ;

: normalize-math-class ( class -- class' )
{
null
Expand Down Expand Up @@ -107,6 +121,7 @@ SYMBOL: history
{ [ dup never-inline-word? ] [ 2drop f ] }
{ [ dup always-inline-word? ] [ inline-word ] }
{ [ dup standard-generic? ] [ inline-standard-method ] }
{ [ dup multi-generic? ] [ inline-multi-method ] }
{ [ dup math-generic? ] [ inline-math-method ] }
{ [ dup inline? ] [ inline-word ] }
[ 2drop f ]
Expand Down
3 changes: 2 additions & 1 deletion basis/hints/hints.factor
@@ -1,7 +1,8 @@
! Copyright (C) 2008, 2010 Slava Pestov.
! See http://factorcode.org/license.txt for BSD license.
USING: accessors arrays assocs byte-arrays byte-vectors classes
combinators definitions fry generic generic.single
combinators definitions fry
generic generic.multi generic.single
generic.standard hashtables io.binary kernel kernel.private math
math.parser parser sbufs sequences sequences.private splitting
strings vectors words ;
Expand Down
7 changes: 5 additions & 2 deletions basis/stack-checker/dependencies/dependencies.factor
@@ -1,8 +1,8 @@
! Copyright (C) 2009, 2010 Slava Pestov.
! See http://factorcode.org/license.txt for BSD license.
USING: accessors alien.c-types arrays assocs classes classes.algebra
classes.algebra.private classes.maybe classes.tuple
combinators.short-circuit fry generic kernel math namespaces sequences
classes.algebra.private classes.dispatch.covariant-tuples classes.maybe
classes.tuple combinators.short-circuit fry generic kernel math namespaces sequences
sets words ;
FROM: classes.tuple.private => tuple-layout ;
IN: stack-checker.dependencies
Expand Down Expand Up @@ -41,6 +41,9 @@ M: anonymous-union add-depends-on-class
M: anonymous-intersection add-depends-on-class
participants>> [ add-depends-on-class ] each ;

M: covariant-tuple add-depends-on-class
classes>> [ add-depends-on-class ] each ;

GENERIC: add-depends-on-c-type ( c-type -- )

M: void add-depends-on-c-type drop ;
Expand Down
1 change: 1 addition & 0 deletions core/bootstrap/syntax.factor
Expand Up @@ -10,6 +10,7 @@ IN: bootstrap.syntax
{
"\""
"("
")"
":"
";"
"<PRIVATE"
Expand Down
3 changes: 2 additions & 1 deletion core/classes/algebra/algebra-tests.factor
@@ -1,4 +1,4 @@
USING: accessors arrays assocs classes classes.algebra
USING: accessors arrays assocs classes classes.algebra classes.algebra.private
classes.tuple classes.union generic generic.private growable
kernel math prettyprint quotations random sbufs sequences
stack-checker strings tools.test vectors words ;
Expand Down Expand Up @@ -361,3 +361,4 @@ TUPLE: xh < xb ;
{ sa } [ sa { sa sb sc } min-class ] unit-test

[ \ + flatten-class ] must-fail

6 changes: 5 additions & 1 deletion core/classes/algebra/algebra.factor
@@ -1,6 +1,6 @@
! Copyright (C) 2004, 2010 Slava Pestov.
! See http://factorcode.org/license.txt for BSD license.
USING: accessors arrays assocs classes classes.private
USING: accessors arrays assocs classes classes.private classes.dispatch
combinators kernel make math math.order namespaces sequences
sets sorting vectors words ;
IN: classes.algebra
Expand Down Expand Up @@ -46,6 +46,8 @@ M: anonymous-complement instance?
M: anonymous-complement class-name
class>> class-name ;

M: dispatch-type rank-class drop 9 ;

DEFER: (class<=)

DEFER: (class-not)
Expand Down Expand Up @@ -181,6 +183,8 @@ PREDICATE: empty-intersection < anonymous-intersection participants>> empty? ;
{ [ dup anonymous-union? ] [ right-anonymous-union<= ] }
{ [ dup anonymous-intersection? ] [ right-anonymous-intersection<= ] }
{ [ dup anonymous-complement? ] [ class>> classes-intersect? not ] }
{ [ over dispatch-type? ] [ left-dispatch<= ] }
{ [ dup dispatch-type? ] [ right-dispatch<= ] }
[ 2drop f ]
} cond
] if
Expand Down
1 change: 1 addition & 0 deletions core/classes/classes.factor
Expand Up @@ -60,6 +60,7 @@ M: class reset-class
"members"
"participants"
"predicate"
"disjoint"
} remove-word-props ;

M: word reset-class drop ;
Expand Down
93 changes: 93 additions & 0 deletions core/classes/classifications/classifications-tests.factor
@@ -0,0 +1,93 @@
USING: classes.algebra classes.classifications classes.classifications.private
combinators.short-circuit compiler.units kernel math namespaces tools.test words
;
IN: classes.classifications.tests

! These are disjoint subsets, with implied test order. First match always wins.
PREDICATE: 8bit < integer { [ 256 < ] } 1&& ;
PREDICATE: 16bit < integer { [ 8bit? not ] [ 65536 < ] } 1&& ;
PREDICATE: unsigned < integer { [ 8bit? not ] [ 16bit? not ] [ 0 > ] } 1&& ;

GENERIC: bounds ( n -- n )
M: 8bit bounds drop 256 ;
M: 16bit bounds drop 65536 ;
M: unsigned bounds drop 1/0. ;

{ 256 } [ 1 bounds ] unit-test
{ 65536 } [ 256 bounds ] unit-test
{ 1/0. } [ 100000 bounds ] unit-test

{ [ { [ 256 < ] } 1&& ] } [ V{ } [ 256 < ] exclude-negations ] unit-test
{ [ { [ 8bit? not ] [ 65536 < ] } 1&& ] } [ V{ 8bit } [ 65536 < ] exclude-negations ] unit-test

DEFER: foo1
DEFER: foo2
DEFER: foo1?
DEFER: foo2?
{ T{ classification-builder f integer V{ { foo1 [ 1 = ] } { foo2 [ 2 = ] } } } }
[ integer [
\ foo1 [ 1 = ] define-classified
\ foo2 [ 2 = ] define-classified
current-classification get
] with-new-classification ] unit-test

{ }
[ [ T{ classification-builder f integer V{ { foo1 [ 1 = ] } { foo2 [ 2 = ] } } }
finalize-classification
] with-compilation-unit ] unit-test

{ t } [ 1 foo1? ] unit-test
{ f } [ 2 foo1? ] unit-test
{ f } [ 1 foo2? ] unit-test
{ t } [ 2 foo2? ] unit-test
{ f } [ 3 foo1? ] unit-test
{ f } [ 3 foo2? ] unit-test

CLASSIFY integer
AS: negative 0 < ;
AS: 0-to-20 20 < ;
AS: 20-to-40 40 < ;
ELSE: above-40

{ HS{ negative 0-to-20 20-to-40 } } [ above-40 "disjoint" word-prop ] unit-test
{ HS{ negative above-40 0-to-20 } } [ 20-to-40 "disjoint" word-prop ] unit-test
{ HS{ 0-to-20 above-40 20-to-40 } } [ negative "disjoint" word-prop ] unit-test

GENERIC: upper ( number -- number )
M: negative upper drop 0 ;
M: 0-to-20 upper drop 20 ;
M: 20-to-40 upper drop 40 ;
M: above-40 upper drop 1/0. ;

PREDICATE: exactly-10 < 0-to-20 10 = ;
M: exactly-10 upper drop 11 ;

{ t } [ 10 exactly-10? ] unit-test
{ t } [ exactly-10 0-to-20 class<= ] unit-test
{ f } [ 0-to-20 exactly-10 class<= ] unit-test

{ 20 } [ 9 upper ] unit-test
{ 11 } [ 10 upper ] unit-test
{ 20 } [ 11 upper ] unit-test
{ 40 } [ 30 upper ] unit-test
{ 1/0. } [ 50 upper ] unit-test

CLASSIFY negative
AS: somewhat-negative -50 > ;
ELSE: very-negative

{ HS{ 0-to-20 above-40 20-to-40 somewhat-negative } } [ very-negative "disjoint" word-prop ] unit-test
{ HS{ 0-to-20 above-40 20-to-40 very-negative } } [ somewhat-negative "disjoint" word-prop ] unit-test

CLASSIFY above-40
AS: below-100 100 < ;
ELSE: very-positive

! Intersection tests
{ f } [ negative 0-to-20 classes-intersect? ] unit-test
{ f } [ very-negative somewhat-negative classes-intersect? ] unit-test
{ f } [ very-positive very-negative classes-intersect? ] unit-test
{ f } [ very-positive 0-to-20 classes-intersect? ] unit-test

{ t } [ very-positive above-40 classes-intersect? ] unit-test
{ t } [ very-positive integer classes-intersect? ] unit-test
60 changes: 60 additions & 0 deletions core/classes/classifications/classifications.factor
@@ -0,0 +1,60 @@
USING: accessors arrays assocs classes.parser classes.predicate
combinators.short-circuit hash-sets kernel namespaces parser sequences words ;

IN: classes.classifications

! Used for ordering of predicate classes

! predicates are { name predicate-quot } pairs
TUPLE: classification-builder base-class predicates ;
: <classification> ( base-class -- ob )
V{ } clone classification-builder boa ;

! PREDICATE: classified-predicate-class < predicate-class "disjoint" word-prop >boolean ;

<PRIVATE
SYMBOL: current-classification
PRIVATE>

: exclude-negations ( seq def -- def' )
[ [ "predicate" word-prop [ not ] compose ] map >array ] dip suffix [ 1&& ] curry ;

! : define-classified-predicate-class ( classifier class superclass definition -- )
! [ define-predicate-class ] keepdd
! "classifier" [ swap prefix ] change-word-prop ;

! Create a predicate class that excludes any previous predicate class
: finalize-classification ( classification-builder -- )
[ predicates>> ] [ base-class>> ] bi
[let :> ( pred-pairs base )
pred-pairs keys :> preds
V{ } clone :> acc
pred-pairs [ first2 :> ( name def )
name base acc def exclude-negations
define-predicate-class
name preds >hash-set make-disjoint
name acc push
] each
! union-name acc >array define-union-class
! union-name acc "classifies" set-word-prop
] ;

: with-new-classification ( base-class quot -- )
swap '[ _ <classification> current-classification set ]
prepose with-scope
; inline

: define-classified ( class quot -- )
2array
current-classification get predicates>> push ;

DEFER: ELSE:
SYNTAX: AS: scan-new-class parse-definition define-classified ;
: (ELSE:) ( -- ) scan-new-class [ drop t ] define-classified ;

SYNTAX: CLASSIFY scan-class
[
\ ELSE: parse-until (ELSE:)
[ "non-empty-parser-accumulator" throw ] unless-empty
current-classification get finalize-classification
] with-new-classification ;
56 changes: 56 additions & 0 deletions core/classes/dispatch/class/class.factor
@@ -0,0 +1,56 @@
USING: accessors arrays classes classes.algebra classes.algebra.private
classes.dispatch classes.private generic kernel make present sequences
stack-checker.dependencies words ;

IN: classes.dispatch.class

! * Dispatching on Classes

! Allows implementing class methods.

TUPLE: class-specializer { class maybe{ class } read-only } ;

C: <class-specializer> class-specializer
INSTANCE: class-specializer dispatch-type

M: class-specializer predicate-def
class>> [ class<= ] curry ;

M: class-specializer class-name class>> present "class<=" prepend ;

M: class-specializer implementor-classes class>> 1array ;

! NOTE: Not yet 100% sure here if the semantics are correct. While the cover of
! a class itself is actually `{ word }`, the cover of a tuple class is '{ tuple }'.
! The main point of this is to make sure that the methods are correctly assigned
! to built-in or tuple classes for dispatch decision tree building. Since
! we want to dispatch on words, this needs to be a word, then...

! NOTE: using classoid to make sure we install dispatchers for everything that
! is comparable using class<=
M: class-specializer (flatten-class) drop classoid (flatten-class) ;

! This implements the actual hierarchy. For class methods, we want to delegate
! to superclasses. Classes and objects are incomparable, so these live in their
! own scope.
M: class-specializer left-dispatch<=
dup class-specializer?
[ right-dispatch<= ] [ 2drop f ] if ;

M: class-specializer right-dispatch<=
over class-specializer?
[ [ class>> ] bi@ class<= ] [ 2drop f ] if ;

! Two classes intersect, if there are objects that can be an instance of both.
! Two class specializers intersect, if their classes intersect. No other
! dispatch type can overlap with a class-specializer (Except for eql
! specializers on class words ?)
M: class-specializer (classes-intersect?)
over class-specializer?
[ [ class>> ] bi@ classes-intersect? ] [ 2drop f ] if ;

M: class-specializer add-depends-on-class
class>> add-depends-on-class ;

M: class-specializer dispatch-depends-on?
class>> class= ;