2
from pypy.tool.sourcetools import compile2
4
class FailedToImplement(Exception):
5
def __init__(self, w_type=None, w_value=None):
10
def raiseFailedToImplement():
11
raise FailedToImplement
14
class MultiMethodTable:
16
def __init__(self, arity, root_class, argnames_before=[], argnames_after=[]):
17
"""NOT_RPYTHON: cannot create new multimethods dynamically.
18
MultiMethod-maker dispatching on exactly 'arity' arguments.
21
raise ValueError, "multimethods cannot dispatch on nothing"
23
self.root_class = root_class
24
self.dispatch_tree = {}
25
self.argnames_before = list(argnames_before)
26
self.argnames_after = list(argnames_after)
28
def register(self, function, *types, **kwds):
29
assert len(types) == self.arity
30
assert kwds.keys() == [] or kwds.keys() == ['order']
31
order = kwds.get('order', 0)
32
node = self.dispatch_tree
33
for type in types[:-1]:
34
node = node.setdefault(type, {})
35
lst = node.setdefault(types[-1], [])
37
lst += [None] * (order+1 - len(lst))
38
assert lst[order] is None, "duplicate function for %r@%d" % (
42
def install(self, prefix, list_of_typeorders, baked_perform_call=True,
43
base_typeorder=None, installercls=None):
44
"NOT_RPYTHON: initialization-time only"
45
assert len(list_of_typeorders) == self.arity
46
installercls = installercls or Installer
47
installer = installercls(self, prefix, list_of_typeorders,
48
baked_perform_call=baked_perform_call,
49
base_typeorder=base_typeorder)
50
return installer.install()
52
def install_if_not_empty(self, prefix, list_of_typeorders,
53
base_typeorder=None, installercls=None):
54
"NOT_RPYTHON: initialization-time only"
55
assert len(list_of_typeorders) == self.arity
56
installercls = installercls or Installer
57
installer = installercls(self, prefix, list_of_typeorders,
58
base_typeorder=base_typeorder)
59
if installer.is_empty():
62
return installer.install()
66
# ____________________________________________________________
67
# limited dict-like interface to the dispatch table
69
def getfunctions(self, types):
70
assert len(types) == self.arity
71
node = self.dispatch_tree
74
return [fn for fn in node if fn is not None]
76
def has_signature(self, types):
78
self.getfunctions(types)
87
def enum_keys(types_so_far, node):
88
for type, subnode in node.items():
89
next_types = types_so_far+(type,)
90
if isinstance(subnode, dict):
91
enum_keys(next_types, subnode)
93
assert len(next_types) == self.arity
94
result.append(next_types)
95
enum_keys((), self.dispatch_tree)
98
# ____________________________________________________________
101
class InstallerVersion1:
110
def __init__(self, multimethod, prefix, list_of_typeorders,
111
baked_perform_call=True, base_typeorder=None):
112
self.__class__.instance_counter += 1
113
self.multimethod = multimethod
114
# avoid prefix clashes, user code should supply different prefixes
115
# itself for nice names in tracebacks
118
while prefix in self.prefix_memo:
120
prefix = "%s%d" % (base_prefix, n)
122
self.prefix_memo[prefix] = 1
123
self.list_of_typeorders = list_of_typeorders
124
self.subtree_cache = {}
126
self.non_empty = self.build_tree([], multimethod.dispatch_tree)
128
self.baked_perform_call = baked_perform_call
131
perform = [(None, prefix, 0)]
135
self.perform_call = self.build_function(None, prefix+'_perform_call',
139
return not self.non_empty
142
#f = open('LOGFILE', 'a')
145
#pprint.pprint(self.list_of_typeorders, f)
146
for target, funcname, func, source, fallback in self.to_install:
147
if target is not None:
148
if hasattr(target, funcname) and fallback:
150
#print >> f, target.__name__, funcname
155
setattr(target, funcname, func)
157
return self.perform_call
159
def build_tree(self, types_so_far, dispatch_node):
160
key = tuple(types_so_far)
161
if key in self.subtree_cache:
162
return self.subtree_cache[key]
164
typeorder = self.list_of_typeorders[len(types_so_far)]
165
for next_type in typeorder:
166
if self.build_single_method(typeorder, types_so_far, next_type,
169
self.subtree_cache[key] = non_empty
172
def build_single_method(self, typeorder, types_so_far, next_type,
174
funcname = '__'.join([self.prefix] + [t.__name__ for t in types_so_far])
176
order = typeorder[next_type]
177
#order = [(next_type, None)] + order
180
for type, conversion in order:
181
if type not in dispatch_node:
182
# there is no possible completion of types_so_far+[type]
183
# that could lead to a registered function.
185
match = dispatch_node[type]
186
if isinstance(match, dict):
187
if self.build_tree(types_so_far+[type], match):
188
call = funcname + '__' + type.__name__
189
call_selfarg_index = len(types_so_far) + 1
190
things_to_call.append((conversion, call,
193
for func in match: # list of functions
195
things_to_call.append((conversion, func, None))
198
funcname = intern(funcname)
199
self.build_function(next_type, funcname, len(types_so_far),
205
def build_function(self, target, funcname, func_selfarg_index,
207
# support for inventing names for the entries in things_to_call
208
# which are real function objects instead of strings
209
miniglobals = {'FailedToImplement': FailedToImplement}
210
def invent_name(obj):
211
if isinstance(obj, str):
215
while name in miniglobals:
217
name = '%s%d' % (obj.__name__, n)
218
miniglobals[name] = obj
221
funcargs = ['arg%d' % i for i in range(self.multimethod.arity)]
224
for conversion, call, call_selfarg_index in things_to_call:
225
callargs = funcargs[:]
226
if conversion is not None:
227
to_convert = func_selfarg_index
228
convert_callargs = (self.multimethod.argnames_before +
229
[callargs[to_convert]])
230
callargs[to_convert] = '%s(%s)' % (
231
invent_name(conversion), ', '.join(convert_callargs))
232
callname = invent_name(call)
233
if call_selfarg_index is not None:
234
# fallback on root_class
235
self.build_function(self.multimethod.root_class,
236
callname, call_selfarg_index, [])
237
callname = '%s.%s' % (callargs.pop(call_selfarg_index), callname)
238
callargs = (self.multimethod.argnames_before +
239
callargs + self.multimethod.argnames_after)
240
bodylines.append('return %s(%s)' % (callname, ', '.join(callargs)))
244
miniglobals['raiseFailedToImplement'] = raiseFailedToImplement
245
bodylines = ['return raiseFailedToImplement()']
249
# protect all lines apart from the last one by a try:except:
250
for i in range(len(bodylines)-2, -1, -1):
251
bodylines[i:i+1] = ['try:',
253
'except FailedToImplement:',
256
if func_selfarg_index is not None:
257
selfargs = [funcargs.pop(func_selfarg_index)]
260
funcargs = (selfargs + self.multimethod.argnames_before +
261
funcargs + self.multimethod.argnames_after)
263
if target is None and not self.baked_perform_call:
264
return funcargs, bodylines[0][len('return '):], miniglobals, fallback
267
bodylines = [' ' + line for line in bodylines]
269
bodylines.insert(0, 'def %s(%s):' % (funcname, ', '.join(funcargs)))
271
source = '\n'.join(bodylines)
273
# XXX find a better place (or way) to avoid duplicate functions
274
l = miniglobals.items()
279
func = self.mmfunccache[key]
281
exec compile2(source) in miniglobals
282
func = miniglobals[funcname]
283
self.mmfunccache[key] = func
285
# print "avoided duplicate function", func
286
self.to_install.append((target, funcname, func, source, fallback))
289
# ____________________________________________________________
290
# Installer version 2
292
class MMDispatcher(object):
294
Explicit dispatcher class. This is not used in normal execution, which
295
uses the complex Installer below to install single-dispatch methods to
296
achieve the same result. The MMDispatcher is only used by
297
rpython.lltypesystem.rmultimethod. It is also nice for documentation.
301
def __init__(self, multimethod, list_of_typeorders):
302
self.multimethod = multimethod
303
self.list_of_typeorders = list_of_typeorders
305
def __call__(self, *args):
306
# for testing only: this is slow
307
i = len(self.multimethod.argnames_before)
308
j = i + self.multimethod.arity
309
k = j + len(self.multimethod.argnames_after)
310
assert len(args) == k
311
prefixargs = args[:i]
312
dispatchargs = args[i:j]
313
suffixargs = args[j:]
314
return self.dispatch([x.__class__ for x in dispatchargs],
319
def dispatch(self, argtypes, prefixargs, args, suffixargs):
320
# for testing only: this is slow
322
if isinstance(v, Call):
323
return v.function(*[expr(w) for w in v.arguments])
327
for v in self.expressions(argtypes, prefixargs, args, suffixargs):
330
except FailedToImplement, e:
333
raise e or FailedToImplement()
335
def expressions(self, argtypes, prefixargs, args, suffixargs):
336
"""Lists the possible expressions that call the appropriate
337
function for the given argument types. Each expression is a Call
338
object. The intent is that at run-time the first Call that doesn't
339
cause FailedToImplement to be raised is the good one.
341
prefixargs = tuple(prefixargs)
342
suffixargs = tuple(suffixargs)
344
def walktree(node, args_so_far):
345
if isinstance(node, list):
348
result.append(Call(func, prefixargs +
352
index = len(args_so_far)
353
typeorder = self.list_of_typeorders[index]
354
next_type = argtypes[index]
355
for target_type, converter in typeorder[next_type]:
356
if target_type not in node:
358
next_arg = args[index]
360
next_arg = Call(converter, prefixargs + (next_arg,))
361
walktree(node[target_type], args_so_far + (next_arg,))
364
walktree(self.multimethod.dispatch_tree, ())
367
def anychance(self, typesprefix):
368
# is there any chance that a list of types starting with typesprefix
369
# could lead to a successful dispatch?
370
# (START-UP TIME OPTIMIZATION ONLY)
371
if self._revcache is None:
373
def build_tree(types_so_far, dispatch_node):
375
typeorder = self.list_of_typeorders[len(types_so_far)]
376
for next_type in typeorder:
377
if build_single_method(typeorder, types_so_far, next_type,
381
self._revcache[types_so_far] = True
384
def build_single_method(typeorder, types_so_far, next_type,
386
order = typeorder[next_type]
387
things_to_call = False
388
for type, conversion in order:
389
if type not in dispatch_node:
390
# there is no possible completion of
391
# types_so_far+[type] that could lead to a
392
# registered function.
394
match = dispatch_node[type]
395
if isinstance(match, dict):
396
if build_tree(types_so_far+(next_type,), match):
397
things_to_call = True
399
things_to_call = True
400
return things_to_call
403
build_tree((), self.multimethod.dispatch_tree)
404
return tuple(typesprefix) in self._revcache
408
""" Represents a call expression.
409
The arguments may themselves be Call objects.
411
def __init__(self, function, arguments):
412
self.function = function
413
self.arguments = arguments
416
class CompressedArray(object):
417
def __init__(self, null_value):
418
self.null_value = null_value
419
self.items = [null_value]
421
def ensure_length(self, newlen):
422
if newlen > len(self.items):
423
self.items.extend([self.null_value] * (newlen - len(self.items)))
425
def insert_subarray(self, array):
426
# insert the given array of numbers into the indexlist,
427
# allowing null values to become non-null
428
if array.count(self.null_value) == len(array):
432
self.ensure_length(test+len(array))
433
for i in range(len(array)):
434
if not (array[i] == self.items[test+i] or
435
array[i] == self.null_value or
436
self.items[test+i] == self.null_value):
440
for i in range(len(array)):
441
if array[i] != self.null_value:
442
self.items[test+i] = array[i]
450
class MRDTable(object):
451
# Multi-Method Dispatch Using Multiple Row Displacement,
452
# Candy Pang, Wade Holst, Yuri Leontiev, and Duane Szafron
453
# University of Alberta, Edmonton AB T6G 2H1 Canada
454
# can be found on http://web.cs.ualberta.ca/~yuri/publ.htm
458
def __init__(self, list_of_types):
459
self.id = MRDTable.Counter
460
MRDTable.Counter += 1
461
self.list_of_types = list_of_types
462
self.typenum = dict(zip(list_of_types, range(len(list_of_types))))
463
self.attrname = '__mrd%d_typenum' % self.id
464
for t1, num in self.typenum.items():
465
setattr(t1, self.attrname, num)
466
self.indexarray = CompressedArray(0)
467
self.strict_subclasses = {}
468
for cls1 in list_of_types:
470
for cls2 in list_of_types:
471
if cls1 is not cls2 and issubclass(cls2, cls1):
473
self.strict_subclasses[cls1] = lst
475
def normalize_length(self, next_array):
476
# make sure that the indexarray is not smaller than any funcarray
477
self.indexarray.ensure_length(len(next_array.items))
480
def invent_name(miniglobals, obj):
481
if isinstance(obj, str):
485
while name in miniglobals:
487
name = '%s%d' % (obj.__name__, n)
488
miniglobals[name] = obj
492
class FuncEntry(object):
494
def __init__(self, bodylines, miniglobals, fallback):
495
self.body = '\n '.join(bodylines)
496
self.miniglobals = miniglobals
497
self.fallback = fallback
498
self.possiblenames = []
500
self._function = None
503
lst = self.miniglobals.items()
505
return self.body, tuple(lst)
507
def get_function_name(self):
508
# pick a name consistently based on self.possiblenames
509
length = min([len(parts) for parts in self.possiblenames])
511
for i in range(length):
513
for parts in self.possiblenames:
514
choices[parts[i]] = True
515
parts = choices.keys()
516
res = str(len(parts))
518
if type(part) is str: # there is a string at this pos
519
if '0_fail' in choices:
521
elif len(parts) == 1:
525
# only types at this location, try to find a common base
527
for cls in parts[1:]:
528
if issubclass(basecls, cls):
530
for cls in parts[1:]:
531
if not issubclass(cls, basecls):
532
break # no common base
534
res = basecls.__name__
536
return '_'.join(result)
538
def make_function(self, fnargs, nbargs_before, mrdtable):
539
if self._function is not None:
540
return self._function
541
name = self.get_function_name()
542
self.compress_typechecks(mrdtable)
543
checklines = self.generate_typechecks(fnargs[nbargs_before:])
547
checklines.append(self.body)
548
body = '\n '.join(checklines)
549
source = 'def %s(%s):\n %s\n' % (name, ', '.join(fnargs), body)
551
if 0: # for debugging the generated mm sources
552
f = open('/tmp/mm-source/%s' % name, 'a')
553
for possiblename in self.possiblenames:
555
for part in possiblename:
556
print >> f, getattr(part, '__name__', part),
562
exec compile2(source) in self.miniglobals
563
self._function = self.miniglobals[name]
564
return self._function
566
def register_valid_types(self, types):
568
for t1 in types[:-1]:
571
node = node.setdefault(t1, {})
574
node[types[-1]] = True
576
def no_typecheck(self):
579
def compress_typechecks(self, mrdtable):
584
for key, subnode in node.items():
588
if fulls == types_total:
591
# a word about subclasses: we are using isinstance() to do
592
# the checks in generate_typechecks(), which is a
593
# compromize. In theory we should check the type ids
594
# instead. But using isinstance() is better because the
595
# annotator can deduce information from that. It is still
596
# correct to use isinstance() instead of type ids in
597
# "reasonable" cases, though -- where "reasonable" means
598
# that classes always have a delegate to their superclasses,
599
# e.g. W_IntObject delegates to W_Root. If this is true
600
# then both kind of checks are good enough to spot
601
# mismatches caused by the compression table.
603
# Based on this reasoning, we can compress the typechecks a
604
# bit more - if we accept W_Root, for example, then we
605
# don't have to specifically accept W_IntObject too.
606
for cls, subnode in node.items():
607
for cls2 in mrdtable.strict_subclasses[cls]:
608
if cls2 in node and node[cls2] == subnode:
613
types_total = len(mrdtable.list_of_types)
614
if full(self.typetree):
617
def generate_typechecks(self, args):
618
def generate(node, level=0):
621
result.append('%s_failedtoimplement = False' % (indent,))
624
result.append('%s_failedtoimplement = True' % (indent,))
627
for key, subnode in node.items():
628
typename = invent_name(self.miniglobals, key)
629
result.append('%s%s isinstance(%s, %s):' % (indent, keyword,
632
generate(subnode, level+1)
634
result.append('%selse:' % (indent,))
635
result.append('%s _failedtoimplement = True' % (indent,))
638
if self.typetree is not True:
639
generate(self.typetree)
640
result.append('if _failedtoimplement:')
641
result.append(' raise FailedToImplement')
645
class InstallerVersion2(object):
651
def __init__(self, multimethod, prefix, list_of_typeorders,
652
baked_perform_call=True, base_typeorder=None):
653
#print 'InstallerVersion2:', prefix
654
self.__class__.instance_counter += 1
655
self.multimethod = multimethod
657
self.list_of_typeorders = list_of_typeorders
658
self.baked_perform_call = baked_perform_call
659
self.mmfunccache = {}
660
args = ['arg%d' % i for i in range(multimethod.arity)]
661
self.fnargs = (multimethod.argnames_before + args +
662
multimethod.argnames_after)
664
# compute the complete table
665
base_typeorder = base_typeorder or list_of_typeorders[0]
666
for typeorder in list_of_typeorders:
668
assert t1 in base_typeorder
670
lst = list(base_typeorder)
674
self.mrdtable = self.mrdtables[key]
676
self.mrdtable = self.mrdtables[key] = MRDTable(key)
678
dispatcher = MMDispatcher(multimethod, list_of_typeorders)
680
def buildtable(prefixtypes):
681
if len(prefixtypes) == multimethod.arity:
682
calllist = dispatcher.expressions(prefixtypes,
683
multimethod.argnames_before,
685
multimethod.argnames_after)
687
self.table[prefixtypes] = calllist
688
elif dispatcher.anychance(prefixtypes):
689
typeorder = list_of_typeorders[len(prefixtypes)]
691
buildtable(prefixtypes + (t1,))
693
self.dispatcher = dispatcher
696
return len(self.table) == 0
699
nskip = len(self.multimethod.argnames_before)
700
null_entry = self.build_funcentry([self.prefix, '0_fail'], [])
701
null_entry.no_typecheck()
703
return self.answer(null_entry)
705
entryarray = CompressedArray(null_entry)
706
indexarray = self.mrdtable.indexarray
707
lst = self.mrdtable.list_of_types
710
def compress(typesprefix, typesnum):
711
if len(typesprefix) == self.multimethod.arity:
712
calllist = self.table.get(typesprefix, [])
713
funcname = [self.prefix]
714
funcname.extend(typesprefix)
715
entry = self.build_funcentry(funcname, calllist)
716
entry.register_valid_types(typesprefix)
718
elif self.dispatcher.anychance(typesprefix):
720
for num1, t1 in enumerate(lst):
721
item = compress(typesprefix + (t1,), typesnum + (num1,))
722
flatline.append(item)
723
if len(typesprefix) == self.multimethod.arity - 1:
727
return array.insert_subarray(flatline)
731
master_index = compress((), ())
733
null_func = null_entry.make_function(self.fnargs, nskip, self.mrdtable)
734
funcarray = CompressedArray(null_func)
735
# round up the length to a power of 2
737
while N < len(entryarray.items):
739
funcarray.ensure_length(N)
740
for i, entry in enumerate(entryarray.items):
741
func = entry.make_function(self.fnargs, nskip, self.mrdtable)
742
funcarray.items[i] = func
743
self.mrdtable.normalize_length(funcarray)
746
#print indexarray.items
747
#print funcarray.items
749
attrname = self.mrdtable.attrname
750
exprfn = "%d" % master_index
751
for n in range(self.multimethod.arity-1):
752
exprfn = "hint(indexarray.items, deepfreeze=True)[%s + arg%d.%s]" % (exprfn, n, attrname)
753
n = self.multimethod.arity-1
754
exprfn = "hint(funcarray.items, deepfreeze=True)[(%s + arg%d.%s) & mmmask]" % (exprfn, n,
756
expr = Call(exprfn, self.fnargs)
757
entry = self.build_funcentry([self.prefix, '0_perform_call'],
759
indexarray = indexarray,
760
funcarray = funcarray,
763
return self.answer(entry)
765
def answer(self, entry):
766
if self.baked_perform_call:
767
nskip = len(self.multimethod.argnames_before)
768
return entry.make_function(self.fnargs, nskip, self.mrdtable)
770
assert entry.body.startswith('return ')
771
expr = entry.body[len('return '):]
772
return self.fnargs, expr, entry.miniglobals, entry.fallback
774
def build_funcentry(self, funcnameparts, calllist, **extranames):
776
if isinstance(v, Call):
777
return '%s(%s)' % (invent_name(miniglobals, v.function),
778
', '.join([expr(w) for w in v.arguments]))
782
fallback = len(calllist) == 0
784
miniglobals = {'raiseFailedToImplement': raiseFailedToImplement}
785
bodylines = ['return raiseFailedToImplement()']
787
miniglobals = {'FailedToImplement': FailedToImplement}
788
miniglobals.update(extranames)
790
for v in calllist[:-1]:
791
bodylines.append('try:')
792
bodylines.append(' return %s' % expr(v))
793
bodylines.append('except FailedToImplement:')
794
bodylines.append(' pass')
795
bodylines.append('return %s' % expr(calllist[-1]))
797
from pypy.rlib.objectmodel import hint
798
miniglobals['hint'] = hint
799
entry = FuncEntry(bodylines, miniglobals, fallback)
802
entry = self.mmfunccache[key]
804
self.mmfunccache[key] = entry
805
entry.possiblenames.append(funcnameparts)
808
# ____________________________________________________________
809
# Selection of the version to use
811
Installer = InstallerVersion1 # modified by translate.py targetpypystandalone