class RuleSet(object): """An observable, stably-ordered collection of rules""" default_action = NoApplicableMethods() default_actiontype = Method def __init__(self, lock=None): self.rules = [] self.actiondefs = {} self.listeners = [] if lock is not None: self.__lock__ = lock synchronized() def add(self, rule): actiondefs = frozenset(self._actions_for(rule)) self.rules.append(rule) self.actiondefs[rule] = actiondefs self._notify(added=actiondefs) synchronized() def remove(self, rule): actiondefs = self.actiondefs.pop(rule) self.rules.remove(rule) self._notify(removed=actiondefs) #def changed(self, rule): # sequence, actions = self.actions[rule] # new_actions = frozenset(self._actions_for(rule, sequence)) # self.actions[rule] = sequence, new_actions # self.notify(new_actions-actions, actions-new_actions) synchronized() def clear(self): actiondefs = frozenset(self) del self.rules[:] self.actiondefs.clear() self._notify(removed=actiondefs) def _notify(self, added=empty, removed=empty): for listener in self.listeners[:]: # must be re-entrant listener.actions_changed(added, removed) synchronized() def __iter__(self): ad = self.actiondefs return iter([a for rule in self.rules for a in ad[rule]]) def _actions_for(self, (na, body, predicate, actiontype, seq)): actiontype = actiontype or self.default_actiontype for signature in disjuncts(predicate): yield Rule(body, signature, actiontype, seq)
def _notify(self, added=empty, removed=empty): for listener in self.listeners[:]: # must be re-entrant listener.actions_changed(added, removed) synchronized() def __iter__(self): ad = self.actiondefs return iter([a for rule in self.rules for a in ad[rule]]) def _actions_for(self, (na, body, predicate, actiontype, seq)): actiontype = actiontype or self.default_actiontype for signature in disjuncts(predicate): yield Rule(body, signature, actiontype, seq) synchronized() def subscribe(self, listener): self.listeners.append(listener) if self.rules: listener.actions_changed(frozenset(self), empty) synchronized() def unsubscribe(self, listener): self.listeners.remove(listener) def _register_rule(gf, pred, context, cls): """Register a rule for `gf` with possible import-deferring""" if not isinstance(gf, basestring): rules = rules_for(gf) rules.add(parse_rule(Dispatching(gf).engine, pred, context, cls))
class Engine(object): """Abstract base for dispatching engines""" reset_on_remove = True def __init__(self, disp): self.function = disp.function self.registry = {} self.closures = {} self.rules = disp.rules self.__lock__ = disp.get_lock() self.argnames = list( flatten(filter(None, inspect.getargspec(self.function)[:3])) ) self.rules.subscribe(self) synchronized() def actions_changed(self, added, removed): if removed and self.reset_on_remove: return self._full_reset() for rule in removed: self._remove_method(rule.predicate, rule) for rule in added: self._add_method(rule.predicate, rule) if added or removed: self._changed() def _changed(self): """Some change to the rules has occurred""" Dispatching(self.function).request_regeneration() def _full_reset(self): """Regenerate any code, caches, indexes, etc.""" self.registry.clear() self.actions_changed(self.rules, ()) Dispatching(self.function).request_regeneration() compiled_cache = None def apply_template(self, template, *args): try: return self.compiled_cache[template, args] except (KeyError, TypeError, AttributeError): pass try: closure = self.closures[template] except KeyError: if getattr(template, CLOSURE): raise TypeError("Templates cannot use outer-scope variables") import linecache; from peak.util.decorators import cache_source tmp = apply_template(template, self.function, *args) body = ''.join(linecache.getlines(getattr(tmp, CODE).co_filename)) filename = "<%s at 0x%08X wrapping %s at 0x%08X>" % ( template.__name__, id(template), self.function.__name__, id(self) ) d ={} exec(compile(body, filename, "exec"), getattr(template, GLOBALS), d) tmp, closure = d.popitem() setattr(closure, DEFAULTS, getattr(template, DEFAULTS)) cache_source(filename, body, closure) self.closures[template] = closure f = closure(self.function, *args) setattr(f, DEFAULTS, getattr(self.function, DEFAULTS)) try: hash(args) except TypeError: pass else: if self.compiled_cache is None: from weakref import WeakValueDictionary self.compiled_cache = WeakValueDictionary() self.compiled_cache[template, args] = f return f def _add_method(self, signature, rule): """Add a case for the given signature and rule""" registry = self.registry action = rule.actiontype(rule.body, signature, rule.sequence) if signature in registry: registry[signature] = combine_actions(registry[signature], action) else: registry[signature] = action return action def _remove_method(self, signature, rule): """Remove the case for the given signature and rule""" raise NotImplementedError def _generate_code(self): """Return a code object for the current state of the function""" raise NotImplementedError
class Dispatching(AddOn): """Manage a generic function's rules, engine, locking, and code""" engine = None def __init__(self, func): func.__doc__ # workaround for PyPy issue #1293 self.function = func self._regen = self._regen_code() # callback to regenerate code self.rules = RuleSet(self.get_lock()) self.backup = None # allows func to call itself during regeneration self.create_engine(TypeEngine) synchronized() def get_lock(self): return self.__lock__ def create_engine(self, engine_type): """Create a new engine of `engine_type`, unsubscribing old""" if self.engine is not None and self.engine in self.rules.listeners: self.rules.unsubscribe(self.engine) self.engine = engine_type(self) return self.engine synchronized() def request_regeneration(self): """Ensure code regeneration occurs on next call of the function""" if self.backup is None: self.backup = getattr(self.function, CODE) setattr(self.function, CODE, self._regen) def _regen_code(self): c = Code.from_function(self.function, copy_lineno=True) c.return_( call_thru( self.function, Call(Getattr( Call(Const(Dispatching), (Const(self.function),), fold=False), '_regenerate' )) ) ) return c.code() synchronized() def as_abstract(self): for action in self.rules: raise AssertionError("Can't make abstract: rules already exist") c = Code.from_function(self.function, copy_lineno=True) c.return_(call_thru(self.function, Const(self.rules.default_action))) if self.backup is None: setattr(self.function, CODE, c.code()) else: self.backup = c.code() return self.function synchronized() def _regenerate(self): func = self.function assert self.backup is not None setattr(func, CODE, self.backup) # ensure re-entrant calls work try: # try to replace the code with new code setattr(func, CODE, self.engine._generate_code()) except: # failure: we'll try to regen again, next time we're called setattr(func, CODE, self._regen) raise else: # success! get rid of the old backup code and return the function self.backup = None return func
def _notify(self, added=empty, removed=empty): for listener in self.listeners[:]: # must be re-entrant listener.actions_changed(added, removed) synchronized() def __iter__(self): ad = self.actiondefs return iter([a for rule in self.rules for a in ad[rule]]) def _actions_for(self, (na, body, predicate, actiontype, seq)): actiontype = actiontype or self.default_actiontype for signature in disjuncts(predicate): yield Rule(body, signature, actiontype, seq) synchronized() def subscribe(self, listener): self.listeners.append(listener) if self.rules: listener.actions_changed(frozenset(self), empty) synchronized() def unsubscribe(self, listener): self.listeners.remove(listener) def _register_rule(gf, pred, context, cls): """Register a rule for `gf` with possible import-deferring""" if not isinstance(gf, basestring):
class IndexedEngine(Engine, TreeBuilder): """A dispatching engine that builds trees using bitmap indexes""" def __init__(self, disp): self.signatures = [] self.all_exprs = {} super(IndexedEngine, self).__init__(disp) self.arguments = dict([(arg, Local(arg)) for arg in self.argnames]) def _add_method(self, signature, rule): signature = Signature(tests_for(signature, self)) if signature not in self.registry: case_id = len(self.signatures) self.signatures.append(signature) requires = [] exprs = self.all_exprs for _t, expr, criterion in tests_for(signature, self): Ordering(self, expr).requires(requires) requires.append(expr) index_type = bitmap_index_type(self, expr) if index_type is not None: if expr not in exprs: exprs[expr] = 1 if always_testable(expr): Ordering(self, expr).requires([]) index_type(self, expr).add_case(case_id, criterion) return super(IndexedEngine, self)._add_method(signature, rule) def _generate_code(self): smig = SMIGenerator(self.function) all_exprs = map(self.to_expression, self.all_exprs) for expr in all_exprs: smig.maybe_cache(expr) memo = dict([(expr, smig.action_id(expr)) for expr in all_exprs]) return smig.generate(self.build_root(memo)).func_code def _full_reset(self): # Replace the entire engine with a new one Dispatching(self.function).create_engine(self.__class__) synchronized() def seed_bits(self, expr, cases): return BitmapIndex(self, expr).seed_bits(cases) synchronized() def reseed(self, expr, criterion): return BitmapIndex(self, expr).reseed(criterion) # Make build() a synchronized method build = synchronized(TreeBuilder.build.im_func) def build_root(self, memo): return self.build( to_bits([len(self.signatures)]) - 1, frozenset(self.all_exprs), memo) def best_expr(self, cases, exprs): return super(IndexedEngine, self).best_expr(list(from_bits(cases)), exprs) def build_node(self, expr, cases, remaining_exprs, memo): return memo[expr], predicate_node_for(self, expr, cases, remaining_exprs, memo) def selectivity(self, expr, cases): return BitmapIndex(self, expr).selectivity(cases) def to_expression(self, expr): return expr def build_leaf(self, cases, memo): action = self.rules.default_action signatures = self.signatures registry = self.registry for case_no in from_bits(cases): action = combine_actions(action, registry[signatures[case_no]]) # No need to memoize here, since the combined action probably isn't # a meaningful key, and template-compiled methods are memoized at a # lower level anyway. return (0, compile_method(action, self))