def from_equivs(cls, equivs): """Form from a sequence of possibly non-disjoint equivalence classes. Equivalence classes of size 1 are ignored. """ part = cls() for eq in equivs: for x, y in pairs(eq): part.equate(x, y) return part
def visit_Compare(self, node): # Result: bool # Cond: compare_helper(left, op, right) for each pair values = (node.left,) + node.comparators values = [self.visit(v) for v in values] for (left, right), op in zip(pairs(values), node.ops): self.compare_helper(node, op, left.type, right.type) return node._replace(left=values[0], comparators=values[1:], type=booltype)
def visit_Compare(self, node): # Result: bool # Cond: compare_helper(left, op, right) for each pair values = (node.left, ) + node.comparators values = [self.visit(v) for v in values] for (left, right), op in zip(pairs(values), node.ops): self.compare_helper(node, op, left.type, right.type) return node._replace(left=values[0], comparators=values[1:], type=booltype)
def visit_Assign(self, node): node = self.generic_visit(node) # Translate multiple assignments as a single statement # (e.g. "a = b = c") into sequential single assignments. if len(node.targets) > 1: stmts = [] values = list(node.targets) + [node.value] for lhs, rhs in reversed(list(pairs(values))): rhs_load = P.ContextSetter.run(rhs, P.Load) stmts.append(P.Assign([lhs], rhs_load)) node = stmts return node
def make_comp_maint_code(spec, resrel, deltarel, op, elem, prefix, *, maint_impl, rc, selfjoin): """Construct comprehension maintenance code. Return the code and a list of maintenance comprehensions used. spec: CompSpec of the comprehension to be computed incrementally. resrel: Name of the relation holding the saved result. deltarel: Name of the updated relation that triggered maintenance. op: Update operation ('add' or 'remove'). elem: AST of the element added or removed to deltarel. prefix: The prefix to use for making fresh local variables. maint_impl: Value to use for the 'impl' option of emitted maintenance comprehensions ('batch' or 'auxonly'). rc: Whether or not the incrementally computed comprehension uses reference counts ('yes', 'no', 'safe'). selfjoin: Strategy for computing self-joins. Possible values: 'sub': use subtractive clauses (Code must be placed after addition / before removal.) 'aug': use augmented clauses (Code must be placed before addition / after removal.) 'das': use a differential assignment set 'assume_disjoint': naive, only valid if joins are disjoint 'assume_disjoint_verify': naive, use das to assert disjoint at runtime """ assert op in ['add', 'remove'] assert maint_impl in ['batch', 'auxonly'] assert rc in ['yes', 'no', 'safe'] assert selfjoin in ['sub', 'aug', 'das', 'assume_disjoint', 'assume_disjoint_verify'] assert deltarel in spec.join.rels if len(spec.params) > 0: raise ValueError('Cannot incrementalize comprehension with ' 'parameters') # Get the maintenance comprehensions. disjoint_strat = (selfjoin if selfjoin in ['sub', 'aug'] else 'das') maint_joins = spec.join.get_maint_joins(elem, deltarel, op, prefix, disjoint_strat=disjoint_strat) maint_comps = [j.to_comp({'impl': maint_impl}) for j in maint_joins] # Get the maintenance joins' enumvars. assert all(j1.enumvars == j2.enumvars for j1, j2 in pairs(maint_joins)) maint_projvars = maint_joins[0].enumvars # Decide whether the body is a normal update or # a reference-counted one. use_rc = {'yes': True, 'no': False, 'safe': not spec.is_duplicate_safe}[rc] if use_rc: op = 'rc' + op resvars = L.VarsFinder.run(spec.resexp, ignore_functions=True) resexp = L.prefix_names(spec.resexp, resvars, prefix) body = L.pc(''' RES.OP(RESEXP) ''', subst={'RES': resrel, '@OP': op, 'RESEXP': resexp}) # Create code according to the choice of self-join strategy. if selfjoin in ['sub', 'aug', 'assume_disjoint']: code = for_rels_union_disjoint_code( maint_projvars, maint_comps, body) else: dasprefix = prefix + 'DAS' ver_dis = selfjoin == 'assume_disjoint_verify' code = for_rels_union_code( maint_projvars, maint_comps, body, dasprefix, verify_disjoint=ver_dis) return code, maint_comps
def make_equalities(self, boundvars): """Opposite of elim_equalities(). Produce a semantically equivalent join in which no enumeration variable appears more than once among the left-hand sides of all enumerators (whether in the same clause or different clauses). Multiple occurrences of the same variable get renamed to fresh variables, with new condition clauses added to equate the fresh variables to the first variable. Variables that appear in boundvars are considered to have occurred once already outside the join. Enumerators in which all enumeration variables have been replaced in this manner get turned into condition clauses. Some occurrences inside enumerators are not replaced, depending on the clause's pat_mask field. """ # Map from each enum var to a counter, used to make fresh # identifiers for its occurrences. repl_counts = defaultdict(lambda: 0) # Map from each enum var to a list of the names that will # be used to replace its occurrences, in order. Occurrences # that should not be replaced map to themselves in the list. # If the var is in boundvars, there is one extra occurrence # at the front of the list. Occurrences inside condition # clauses are not accounted for in the list. repl_map = defaultdict(lambda: []) def add_repl_occ(v): """Process an occurrence that is subject to renaming.""" # Ignore wildcards. if v == '_': return # First occurrence is not renamed, later occurrences are. repl_counts[v] += 1 occ_name = (v if repl_counts[v] == 1 else v + '_' + str(repl_counts[v])) repl_map[v].append(occ_name) def add_skip_occ(v): """Process an occurrence that is left unchanged.""" # Wildcards should not occur. # (Not sure about this, could make it just skip, like above.) assert v != '_' repl_map[v].append(v) for v in boundvars: add_repl_occ(v) # Process occurrences in the clauses. In addition, # all-bound enumerators get turned into condition clauses. new_clauses = [] for cl in self.clauses: # Skip conditions. if cl.kind is Clause.KIND_COND: new_clauses.append(cl) continue # All-bound enum becomes a condition. if set(cl.enumlhs).issubset(repl_counts): cl = self.factory.enum_to_membercond(cl) new_clauses.append(cl) continue # Normal case. for v, p in zip(cl.enumlhs, cl.pat_mask): if p: add_repl_occ(v) else: add_skip_occ(v) new_clauses.append(cl) # Create new condition clauses to equate the new variables. new_conds = [] for v in self.enumvars: repl_list = repl_map[v] for v1, v2 in pairs(repl_list): # No equality needed for identity replacements. if v == v2: continue condcl = self.factory.from_AST(L.cmpeq(L.ln(v1), L.ln(v2))) new_conds.append(condcl) # Define a substitution that calls a function to consume # the next identifier from the appropriate list. # For boundvars, start replacing at the second slot onward. for v in repl_map: if v in boundvars: repl_map[v].pop(0) def var_renamer(v): return repl_map[v].pop(0) # Use rewrite_lhs(), not rewrite_subst(). # We don't want to rewrite demparams, for instance. subst = {v: var_renamer for v in self.enumvars} new_clauses = [self.factory.rewrite_lhs(cl, subst) if cl.kind is Clause.KIND_ENUM else cl for cl in new_clauses] # Insert each new condition clause immediately after both # equated variables have been seen. # For each clause in new_join, pull in the applicable cond # clauses and delete them from the new_conds list. new_clauses_with_conds = [] seenvars = set(boundvars) for cl in new_clauses: new_clauses_with_conds.append(cl) seenvars.update(cl.enumvars) for condcl in list(new_conds): lhs, rhs = condcl.eqvars if lhs in seenvars and rhs in seenvars: new_conds.remove(condcl) new_clauses_with_conds.append(condcl) assert len(new_conds) == 0 return self._replace(clauses=new_clauses_with_conds)
def make_equalities(self, boundvars): """Opposite of elim_equalities(). Produce a semantically equivalent join in which no enumeration variable appears more than once among the left-hand sides of all enumerators (whether in the same clause or different clauses). Multiple occurrences of the same variable get renamed to fresh variables, with new condition clauses added to equate the fresh variables to the first variable. Variables that appear in boundvars are considered to have occurred once already outside the join. Enumerators in which all enumeration variables have been replaced in this manner get turned into condition clauses. Some occurrences inside enumerators are not replaced, depending on the clause's pat_mask field. """ # Map from each enum var to a counter, used to make fresh # identifiers for its occurrences. repl_counts = defaultdict(lambda: 0) # Map from each enum var to a list of the names that will # be used to replace its occurrences, in order. Occurrences # that should not be replaced map to themselves in the list. # If the var is in boundvars, there is one extra occurrence # at the front of the list. Occurrences inside condition # clauses are not accounted for in the list. repl_map = defaultdict(lambda: []) def add_repl_occ(v): """Process an occurrence that is subject to renaming.""" # Ignore wildcards. if v == '_': return # First occurrence is not renamed, later occurrences are. repl_counts[v] += 1 occ_name = (v if repl_counts[v] == 1 else v + '_' + str(repl_counts[v])) repl_map[v].append(occ_name) def add_skip_occ(v): """Process an occurrence that is left unchanged.""" # Wildcards should not occur. # (Not sure about this, could make it just skip, like above.) assert v != '_' repl_map[v].append(v) for v in boundvars: add_repl_occ(v) # Process occurrences in the clauses. In addition, # all-bound enumerators get turned into condition clauses. new_clauses = [] for cl in self.clauses: # Skip conditions. if cl.kind is Clause.KIND_COND: new_clauses.append(cl) continue # All-bound enum becomes a condition. if set(cl.enumlhs).issubset(repl_counts): cl = self.factory.enum_to_membercond(cl) new_clauses.append(cl) continue # Normal case. for v, p in zip(cl.enumlhs, cl.pat_mask): if p: add_repl_occ(v) else: add_skip_occ(v) new_clauses.append(cl) # Create new condition clauses to equate the new variables. new_conds = [] for v in self.enumvars: repl_list = repl_map[v] for v1, v2 in pairs(repl_list): # No equality needed for identity replacements. if v == v2: continue condcl = self.factory.from_AST(L.cmpeq(L.ln(v1), L.ln(v2))) new_conds.append(condcl) # Define a substitution that calls a function to consume # the next identifier from the appropriate list. # For boundvars, start replacing at the second slot onward. for v in repl_map: if v in boundvars: repl_map[v].pop(0) def var_renamer(v): return repl_map[v].pop(0) # Use rewrite_lhs(), not rewrite_subst(). # We don't want to rewrite demparams, for instance. subst = {v: var_renamer for v in self.enumvars} new_clauses = [ self.factory.rewrite_lhs(cl, subst) if cl.kind is Clause.KIND_ENUM else cl for cl in new_clauses ] # Insert each new condition clause immediately after both # equated variables have been seen. # For each clause in new_join, pull in the applicable cond # clauses and delete them from the new_conds list. new_clauses_with_conds = [] seenvars = set(boundvars) for cl in new_clauses: new_clauses_with_conds.append(cl) seenvars.update(cl.enumvars) for condcl in list(new_conds): lhs, rhs = condcl.eqvars if lhs in seenvars and rhs in seenvars: new_conds.remove(condcl) new_clauses_with_conds.append(condcl) assert len(new_conds) == 0 return self._replace(clauses=new_clauses_with_conds)