After implementing inline slot caches I tried various approaches for caching method lookup. At first I thought I could store a global hash table which was keyed by combining the hash values of the types of the arguments passed to a function. Then by comparing the arguments types with the types stored in the table entries I could find the correct method to dispatch to. This turned out to be slower than my current implementation which simply walks the cons lists that describe each method’s type pattern and calling “type?” to test whether the argument is a subtype of the target type.
My eventual solution was to turn this lookup process into straight-line code by dynamically creating machine code to execute these tests.
Consider the following Church code:
length s:string ...... length n:nil 0 length l:cons ...... length a:array ......
Here we check the type of the argument to distinguish between nil, strings, cons-lists and arrays. For nil and cons types we can generate an efficient check against the object tag, for strings and arrays we call out to “type?”. The following state code is generated for these tests:
(DEFINE DISPATCH-MATCHER31 (LAMBDA (ARG1 ARG2 ARG3 ARG4 ARG5 ARG6 ARG7) (LET ((ARG-COUNT (LOAD-ARGUMENT-COUNT))) (IF (= ARG-COUNT 1) (BEGIN (IF (AND (= (BAND ARG1 LOWTAG_BITS) TAG_REF) (CHURCH-IF (TYPE? ARG1 137075987) 1 0)) (RETURN (CALL-C 134993347 1 (LOAD-CLOSURE-POINTER) ARG1))) (IF (AND (= (BAND ARG1 LOWTAG_BITS) TAG_REF) (CHURCH-IF (TYPE? ARG1 137075507) 1 0)) (RETURN (CALL-C 134968518 1 (LOAD-CLOSURE-POINTER) ARG1))) (IF (= (BAND ARG1 LOWTAG_BITS) TAG_CONS) (RETURN (CALL-C 134968094 1 (LOAD-CLOSURE-POINTER) ARG1))) (IF (= ARG1 TAG_NIL) (RETURN (CALL-C 134968046 1 (LOAD-CLOSURE-POINTER) ARG1)))))) (CHURCH-DISPATCH-MATCHER-FAILED 1))))
The first two tests check for the TAG_REF tag which is used for normal objects. If the tag matches we call “type?” with the literal address representing the class of the target type. If the test matches we can call the associated method directly.
The other two tests only compare the tag, making it quite efficient for primitive types like cons, fixnums, true and nil.
After generating this code the calling function is patched to jump to this matcher method directly.
Together with the inline slot caches these modifications speed the system up by about a factor of two.
Future work involves determining more precisely when to compile these dispatchers (at the moment I do it after a symbol has been dispatched through 5000 times) and optimizing the generated tests. (Even in this example there is redundant check for TAG_REF). It is probably also possible to be more efficient when overlapping types are involved.