def create_single_conn(s, chan, scope, i_elem, elem): debug(self.debug, 'New connection for index {}'.format(elem.index)) master = tab.lookup_is_master(chan, elem, scope) if master: location = ast.ExprSingle( ast.ElemNumber( tab.lookup_slave_location(chan.name, elem.index, scope))) else: location = ast.ExprSingle( ast.ElemNumber( tab.lookup_master_location(chan.name, elem.index, scope))) chanend = ast.ElemId(chan.chanend) chanend.symbol = Symbol(chan.chanend, self.chanend_type(chan), None, scope=T_SCOPE_PROC) connid = tab.lookup_connid(chan.name, elem.index, scope) chanid = ast.ExprSingle(ast.ElemNumber(connid)) cond = ast.ExprBinop( '=', i_elem, ast.ExprSingle(ast.ElemNumber(elem.indices_value))) conn = ast.StmtConnect(chanend, chanid, location, self.connect_type(chan, master)) return ast.StmtIf(cond, conn, s) if s != None else conn
def form_location(sym, base, offset, compression): """ Given the base id (ElemId), offset (Expr) and compression ratio (integer), produce an expression for a location:: location = (base + (off/comp)) rem NUM_CORES """ assert isinstance(base, ast.Elem) or isinstance(base, ast.Expr) assert isinstance(offset, ast.Expr) if isinstance(base, ast.Expr): base = ast.ElemGroup(base) # Offset loc = offset # Apply compression #if compression > 1: # loc = ast.ExprBinop('/', ast.ElemGroup(loc), # ast.ExprSingle(ast.ElemNumber(compression))) #elem_numcores = ast.ElemId(SYS_NUM_CORES_CONST) #elem_numcores.symbol = sym.lookup(SYS_NUM_CORES_CONST) # Apply 'rem NUM_CORES' to base + (off/comp) loc = ast.ExprSingle( ast.ElemGroup( ast.ExprBinop('+', base, ast.ExprSingle(ast.ElemGroup(loc))))) #ast.ExprBinop('rem', ast.ExprSingle(elem_numcores)) v = EvalExpr().expr(loc) loc = loc if v == None else ast.ExprSingle(ast.ElemNumber(v)) return loc
def create_range_conn(s, chan, i_elem, group): diff2 = group[0] elem0 = group[1][0][0] offset = target_loc(chan, elem0, scope) # Form the location expression if elem0.indices_value > 0: location = ast.ElemGroup( ast.ExprBinop( '-', i_elem, ast.ExprSingle(ast.ElemNumber(elem0.indices_value)))) else: location = i_elem location = ast.ExprBinop('*', ast.ElemNumber(diff2 + 1), ast.ExprSingle(location)) location = ast.ExprBinop('+', ast.ElemGroup(location), ast.ExprSingle(ast.ElemNumber(offset))) chanend = ast.ElemId(chan.chanend) chanend.symbol = Symbol(chan.chanend, self.chanend_type(chan), None, scope=T_SCOPE_PROC) connid = tab.lookup_connid(chan.name, elem0.index, scope) chanid = ast.ExprSingle(ast.ElemNumber(connid)) begin = elem0.indices_value end = group[1][-1][0].indices_value cond = ast.ExprBinop( '>=', i_elem, ast.ExprSingle(ast.ElemNumber(min(begin, end)))) master = tab.lookup_is_master(chan, elem0, scope) conn = ast.StmtConnect(chanend, chanid, location, self.connect_type(chan, master)) return ast.StmtIf(cond, conn, s) if s else conn
def indices_expr(indices): """ Given a set of indices, return an expression computing their combined value. """ dims = [x.count_value for x in indices] r = None for (i, x) in enumerate(indices): c = reduce(lambda x, y: x * y, dims[i + 1:], 1) c_expr = ast.ExprSingle(ast.ElemNumber(c)) eid = ast.ElemId(x.name) eid.symbol = Symbol(x.name, T_VAL_SINGLE, None, scope=T_SCOPE_PROC) e = ast.ExprBinop('*', eid, c_expr) if c > 1 else ast.ExprSingle(eid) r = e if r == None else ast.ExprBinop('+', ast.ElemGroup(r), e) return r
def create_tree_conn(tab, scope, chan, phase, group_size, base_indices_value, loc_base, loc_diff, connid_min, connid_offset, connid_diff, i_elem): location = ast.ExprBinop( '-', i_elem, ast.ExprSingle(ast.ElemNumber(base_indices_value))) location = ast.ExprBinop( '/', ast.ElemGroup(location), ast.ExprSingle(ast.ElemNumber(group_size))) location = ast.ExprBinop('*', ast.ElemNumber(loc_diff), ast.ExprSingle(ast.ElemGroup(location))) location = ast.ExprBinop('+', ast.ElemNumber(loc_base), ast.ExprSingle(ast.ElemGroup(location))) chanend = ast.ElemId(chan.chanend) chanend.symbol = Symbol(chan.chanend, self.chanend_type(chan), None, scope=T_SCOPE_PROC) elem0 = chan.elems[phase] #connid = ast.ExprBinop('+', i_elem, # ast.ExprSingle(ast.ElemNumber(connid_offset))) connid = ast.ExprBinop('-', i_elem, ast.ExprSingle(ast.ElemNumber(phase))) connid = ast.ExprBinop('rem', ast.ElemGroup(connid), ast.ExprSingle(ast.ElemNumber(group_size))) connid = ast.ExprBinop('*', ast.ElemGroup(connid), ast.ExprSingle(ast.ElemNumber(connid_diff))) connid = ast.ExprBinop('+', ast.ElemGroup(connid), ast.ExprSingle(ast.ElemNumber(connid_min))) master = tab.lookup_is_master(chan, elem0, scope) return ast.StmtConnect(chanend, connid, location, self.connect_type(chan, master))
def insert(self, stmt): # Create the assignment e = ast.ElemId(PROC_ID_VAR) e.symbol = Symbol(PROC_ID_VAR, T_VAR_SINGLE, None, T_SCOPE_PROC) s = ast.StmtAss(e, ast.ExprSingle(ast.ElemFcall('procid', []))) # Add the assignent in sequence with the existing body process #if isinstance(node.stmt, ast.StmtSeq): # node.stmt.stmt.insert(0, s) #else: return ast.StmtSeq([s, stmt])
def gen_single_conn(self, tab, scope, chan): """ Generate a connection for a single channel declaration. 'chan' is a ChanElemSet with one element. """ elem = chan.elems[0] chanend = ast.ElemId(chan.chanend) chanend.symbol = Symbol(chan.chanend, self.chanend_type(chan), None, scope=T_SCOPE_PROC) connid = tab.lookup_connid(chan.name, elem.index, scope) chanid = ast.ExprSingle(ast.ElemNumber(connid)) # Server-end connections don't need targets if chan.symbol.scope == T_SCOPE_SERVER: return ast.StmtConnect(chanend, chanid, ast.ExprSingle(ast.ElemNumber(0)), self.connect_type(chan, False)) # All other connections do else: master = tab.lookup_is_master(chan, elem, scope) if master: location = ast.ExprSingle( ast.ElemNumber( tab.lookup_slave_location(chan.name, elem.index, scope))) else: location = ast.ExprSingle( ast.ElemNumber( tab.lookup_master_location(chan.name, elem.index, scope))) chanend = ast.ElemId(chan.chanend) chanend.symbol = Symbol(chan.chanend, self.chanend_type(chan), None, scope=T_SCOPE_PROC) return ast.StmtConnect(chanend, chanid, location, self.connect_type(chan, master))
def rename_chan(self, elem, chans): """ Rename a channel (elem) using the set of channel elements (ChanElemSet) which will contain the name of the channel end allocated to this instance. """ if isinstance(elem, ast.ElemId) and elem.symbol.type == T_CHAN_SINGLE: for x in chans: if elem.name == x.name: t = T_CHANEND_SINGLE if x.symbol.scope == T_SCOPE_SERVER: t = T_CHANEND_SERVER_SINGLE elif x.symbol.scope == T_SCOPE_CLIENT: t = T_CHANEND_CLIENT_SINGLE s = Symbol(x.chanend, t, T_SCOPE_PROC) e = ast.ElemId(x.chanend) e.symbol = s #print('renamed {} to {} as type {}'.format(x.name, x.chanend, t)) return ast.ExprSingle(e) elif isinstance(elem, ast.ElemSub) and elem.symbol.type == T_CHAN_ARRAY: for x in chans: if elem.name == x.name and CmpExpr().expr(elem.expr, x.expr): t = T_CHANEND_SINGLE if x.symbol.scope == T_SCOPE_SERVER: t = T_CHANEND_SERVER_SINGLE elif x.symbol.scope == T_SCOPE_CLIENT: t = T_CHANEND_CLIENT_SINGLE s = Symbol(x.chanend, t, T_SCOPE_PROC) e = ast.ElemId(x.chanend) e.symbol = s #print('renamed {} to {} as type {}'.format(x.name, x.chanend, t)) return ast.ExprSingle(e) else: # Don't worry about chanends return ast.ExprSingle(elem)
def stmt_server(self, node, parent, d): """ For server statements the server and client processes are overlaid. """ if node.distribute: debug(self.debug, 'd before server = {}'.format(d)) e = self.stmt(node.server, parent, d) debug(self.debug, 'd after server = {}'.format(e)) node.client = ast.StmtOn(ast.ExprSingle(ast.ElemNumber(d)), node.client) e += self.stmt(node.client, parent, d) debug(self.debug, 'd after client = {}'.format(e)) node.distribute = False return e else: debug(self.debug, 'd before server = {}'.format(d)) x = self.stmt(node.server, parent, d) debug(self.debug, 'd after server = {}'.format(x)) y = self.stmt(node.client, parent, d) debug(self.debug, 'd after client = {}'.format(y)) return max(x, y)
def stmt_par(self, node, parent, d): """ For processes in parallel composition, add 'on' prefixes to provide simple compile-time distribution. If any process is already prefixed with an 'on', then do not add any (this is mainly for the test cases). """ if node.distribute: if any([isinstance(x, ast.StmtOn) for x in node.stmt]): self.errorlog.report_error( "parallel composition contains 'on's") return 0 e = self.stmt(node.stmt[0], parent, d) debug(self.debug, 'd before par = {}'.format(d)) for (i, x) in enumerate(node.stmt[1:]): node.stmt[i + 1] = ast.StmtOn( ast.ExprSingle(ast.ElemNumber(d + e)), x) e += self.stmt(x, parent, d + e) debug(self.debug, 'd after par = {}'.format(d)) node.distribute = False return e else: return 0
def defn(self, node): node.location = ast.ElemNumber(0) if node.name == 'main': self.stmt(node.stmt, ast.ExprSingle(node.location)) else: self.stmt(node.stmt, ast.ExprSingle(node.location))
def stmt_on(self, node, l): node.location = l # Try and evaluate this 'target' expression v = EvalExpr().expr(node.expr) k = ast.ExprSingle(ast.ElemNumber(v)) if v != None else l self.stmt(node.stmt, k)
def p_expr_single(self, p): 'expr : elem' p[0] = ast.ExprSingle(p[1])
def distribute_stmt(self, m, elem_t, elem_n, elem_m, base, indices, proc_actuals, formals, pcall): """ Create the distribution process body statement. """ # Setup some useful expressions name = self.sig.unique_process_name() elem_x = ast.ElemId('_x') expr_x = ast.ExprSingle(elem_x) expr_t = ast.ExprSingle(elem_t) expr_n = ast.ExprSingle(elem_n) expr_m = ast.ExprSingle(elem_m) elem_base = ast.ElemNumber(base) expr_base = ast.ExprSingle(elem_base) # Replace ocurrances of index variables i with i = f(_t) divisor = m for x in indices: divisor = floor(divisor / x.count_value) # Calculate the index i as a function of _t and the dimensions. e = ast.ExprBinop( 'rem', ast.ElemGroup( ast.ExprBinop('/', elem_t, ast.ExprSingle(ast.ElemNumber(divisor)))), ast.ExprSingle(ast.ElemNumber(x.count_value))) if x.base_value > 0: e = ast.ExprBinop('+', ast.ElemNumber(x.base_value), ast.ExprSingle(ast.ElemGroup(e))) # Then replace it for each ocurrance of i for y in pcall.args: y.accept(SubElem(ast.ElemId(x.name), ast.ElemGroup(e))) d = ast.ExprBinop('+', elem_t, ast.ExprSingle(elem_x)) d = form_location(self.sym, elem_base, d, 1) # Create on the on statement on_stmt = ast.StmtOn( d, ast.StmtPcall(name, [ ast.ExprBinop('+', elem_t, ast.ExprSingle(elem_x)), expr_x, ast.ExprBinop('-', elem_m, ast.ExprSingle(elem_x)) ] + proc_actuals)) on_stmt.location = None # Conditionally recurse {d()|d()} or d() s1 = ast.StmtIf( # if m > n/2 ast.ExprBinop('>', elem_m, ast.ExprSingle(elem_x)), # then ast.StmtPar( [], [ # on id()+t+n/2 do d(t+n/2, n/2, m-n/2, ...) on_stmt, # d(t, n/2, n/2) ast.StmtPcall(name, [expr_t, expr_x, expr_x] + proc_actuals), ], False), # else d(t, n/2, m) ast.StmtPcall(name, [expr_t, expr_x, expr_m] + proc_actuals)) # _x = n/2 ; s1 n_div_2 = ast.ExprBinop('>>', elem_n, ast.ExprSingle(ast.ElemNumber(1))) s2 = ast.StmtSeq([], [ast.StmtAss(elem_x, n_div_2), s1]) # if n = 1 then process() else s1 s3 = ast.StmtIf( ast.ExprBinop('=', elem_n, ast.ExprSingle(ast.ElemNumber(1))), pcall, s2) # Create the local declarations decls = [ast.VarDecl(elem_x.name, T_VAR_SINGLE, None)] s4 = ast.StmtSeq(decls, [s3]) # Create the definition d = ast.ProcDef(name, T_PROC, formals, s4) return d
def transform_rep(self, stmt): """ Convert a replicated parallel statement into a divide-and-conquer form. - Return the tuple (process-def, process-call) We only allow replicators where their location is known; i.e. stmt.location is an Expr(Number(x)). """ assert isinstance(stmt, ast.StmtRep) assert isinstance(stmt.stmt, ast.StmtPcall) assert isinstance(stmt.location, ast.ExprSingle) assert isinstance(stmt.location.elem, ast.ElemNumber) pcall = stmt.stmt # The context of the procedure call is each variable occurance in the # set of arguments. context = FreeVars().compute(pcall) assert not stmt.m == None #assert not stmt.f == None n = util.next_power_of_2(stmt.m) # Create new variables formals = [] # Formals for the new distribution process actuals = [] # Actuals for the new distribution process proc_actuals = [] # All other live-in variables elem_t = ast.ElemId('_t') # Interval base elem_n = ast.ElemId('_n') # Interval width elem_m = ast.ElemId('_m') # Processes in interval #print(Printer().expr(stmt.location)) base = stmt.location.elem.value # Populate the distribution and replicator indices formals.append(ast.Param('_t', T_VAL_SINGLE, None)) formals.append(ast.Param('_n', T_VAL_SINGLE, None)) formals.append(ast.Param('_m', T_VAL_SINGLE, None)) actuals.append(ast.ExprSingle(ast.ElemNumber(0))) actuals.append(ast.ExprSingle(ast.ElemNumber(n))) actuals.append(ast.ExprSingle(ast.ElemNumber(stmt.m))) # For each non-index free-variable of the process call for x in context - set([x for x in stmt.indices]): # Add each unique variable ocurrance from context as a formal param formals.append( ast.Param(x.name, rep_var_to_param[x.symbol.type], x.symbol.expr)) # If the actual is an array subscript or slice, we only pass the id. if isinstance(x, ast.ElemSlice) or isinstance(x, ast.ElemSub): e = ast.ElemId(x.name) e.symbol = x.symbol proc_actuals.append(ast.ExprSingle(e)) else: proc_actuals.append(ast.ExprSingle(copy.copy(x))) # Add the extra actual params to the distribution actuals actuals.extend(proc_actuals) # Create the process definition and perform semantic analysis to # update symbol bindings. d = self.distribute_stmt(stmt.m, elem_t, elem_n, elem_m, base, stmt.indices, proc_actuals, formals, pcall) #Printer().defn(d, 0) self.sem.defn(d) # Create the corresponding call. c = ast.StmtPcall(d.name, actuals) self.sig.insert(d.type, d) return (d, c)
def p_right_single(self, p): 'right : elem' p[0] = ast.ExprSingle(p[1])
def stmt_to_process(self, stmt, indices=[]): """ Convert a statement into a process definition. - Create the definition node. - Create the corresponding Pcall node. - Insert the definition into the signature table. - Return the tuple (process-def, process-call) We pass process definitions recursively up the AST. Sets for live-in and local decls (for non-live, non-array targets). We want to add formal parameters for any live-in variable and for any variable that is both live-in and live-out, for a statement s:: Live-over(s) = (live-in(s) | live-out(s)) & free(s) where | is set union and & is set intersection. Also, the index varible (rep_var) of replicator statements must be treated as a value and not a variable in its use as a parameter (transform replicator stage) and passed-by-reference. """ assert isinstance(stmt, ast.Stmt) #out = set() #[out.update(y.inp) for y in succ.pred] #print('Successors: {}'.format(' '.join(['{}'.format(x.pred) for x in succ]))) #[print(y.out) for y in succ.pred] out = LiveOut().compute(stmt) free = FreeVars().compute(stmt) live = free & (stmt.inp | out) local_decls = free - live debug(self.debug, '==========================================') debug(self.debug, 'Free: {}'.format(free)) debug(self.debug, 'Live-in: {}'.format(stmt.inp)) debug(self.debug, 'Live-out: {}'.format(out)) debug(self.debug, 'live-over: {}'.format(live)) debug(self.debug, 'Local decls: {}'.format(local_decls)) #Printer().stmt(stmt) # Create the formal and actual paramerer and local declaration lists formals = [] formal_set = set() actuals = [] decls = [] # Deal with the index variable of a replicator statement: add it as a # single value (not a variable) to the formals and as-is to the actuals, # then remove it from the variable live-in set and locals so it is not # declared again. for x in indices: formals.append(ast.Param(x.name, T_VAL_SINGLE, None)) formal_set.add(x) actuals.append(ast.ExprSingle(ast.ElemId(x.name))) live -= set([x]) local_decls -= set([x]) # Remove values from local declarations s = set() for x in local_decls: if not( x.symbol.type == T_VAL_SINGLE and \ (x.symbol.scope == T_SCOPE_PROGRAM or \ x.symbol.scope == T_SCOPE_SYSTEM)): s.add(x) local_decls = s # Replicated parallel statements are more restrivtive var_to_param = rep_var_to_param if len( indices) > 0 else par_var_to_param # For each variable in the live-in set add accordingly to formals and actuals. for x in live: # Don't include constant values. if x.symbol.type == T_VAL_SINGLE and \ (x.symbol.scope == T_SCOPE_PROGRAM or \ x.symbol.scope == T_SCOPE_SYSTEM): continue # All parameters are added as formals with the appropriate conversion p = ast.Param(x.name, var_to_param[x.symbol.type], x.symbol.expr) p.symbol = x.symbol formals.append(p) formal_set.add(x) # If the actual is an array subscript or slice, we only pass the id. if isinstance(x, ast.ElemSlice) or isinstance(x, ast.ElemSub): e = ast.ElemId(x.name) e.symbol = x.symbol actuals.append(ast.ExprSingle(e)) else: actuals.append(ast.ExprSingle(copy.copy(x))) # For arrays with lengths specified with variables (i.e. arrays passed by # reference) then we must include the length as the next parameter as long # as it is not already in the live set OR the formals and it's not a # defined value. if (x.symbol.type.form == 'array' and isinstance(x.symbol.expr, ast.ExprSingle) and not x.symbol.expr.elem in live and not x.symbol.expr.elem in formal_set and x.symbol.value == None): p = ast.Param(x.symbol.expr.elem.name, T_VAL_SINGLE, None) formals.append(p) formal_set.add(x.symbol.expr.elem) actuals.append(x.symbol.expr) # Create a unique name name = self.sig.unique_process_name() # Create the local declarations (excluding values) [decls.append(self.create_decl(x)) for x in local_decls] # Create the new process definition if isinstance(stmt, ast.StmtSeq) or isinstance(stmt, ast.StmtPar): stmt.decls += decls else: stmt = ast.StmtSeq(decls, [stmt]) d = ast.ProcDef(name, T_PROC, formals, stmt) # perform semantic analysis to update symbol bindings. if self.debug: Printer().defn(d, 0) debug(self.debug, '==========================================') self.sem.defn(d) # Create the corresponding call. c = ast.StmtPcall(name, actuals) return (d, c)