/* * Attempts to replace iteration over simple anonymous ranges with calls to * direct iterators that take low, high and stride as arguments. This is to * avoid the cost of constructing ranges, and if the stride is known at compile * time, provide a more optimized iterator that uses "<, <=, >, or >=" as the * relational operator. * * This is only meant to replace anonymous range iteration for "simple" bounded * ranges. Simple means it's a range of the form "low..high" or "low..high by * stride". Anything more complex is ignored with the thinking that this should * optimize the most common range iterators, but it could be expanded to handle * more cases. * * An alternative is to update scalar replacement of aggregates to work on * ranges, which should be able to achieve similar results as this optimization * while handling all ranges, including non-anonymous ranges. * * Will optimize things like: * - "for i in 1..10" * - "for i in 1..10+1" * - "var lo=1, hi=10;for i in lo..hi" * - "for i in 1..10 by 2" * - "for (i, j) in zip(1..10 by 2, 1..10 by -2)" * - "for (i, j) in zip(A, 1..10 by 2)" // will optimize range iter still * - "coforall i in 1..10 by 2" // works for coforalls as well * * Will not optimize ranges like: * - "for in in (1..)" // doesn't handle unbounded ranges * - "for i in 1..10 by 2 by 2" // doesn't handle more than one by operator * - "for i in 1..10 align 2" // doesn't handle align operator * - "for i in 1..#10" // doesn't handle # operator * - "var r = 1..10"; for i in r" // not an anonymous range * - "forall i in 1..10" // does not get applied to foralls * * Note that this function is pretty fragile because it relies on names of * functions/iterators as well as the arguments and order of those * functions/iterators but there's not really a way around it this early in * compilation. If the iterator can't be replaced, it is left unchanged. */ static void tryToReplaceWithDirectRangeIterator(Expr* iteratorExpr) { CallExpr* range = NULL; Expr* stride = NULL; if (CallExpr* call = toCallExpr(iteratorExpr)) { // grab the stride if we have a strided range if (call->isNamed("chpl_by")) { range = toCallExpr(call->get(1)->copy()); stride = toExpr(call->get(2)->copy()); } // assume the call is the range (checked below) and set default stride else { range = call; stride = new SymExpr(new_IntSymbol(1)); } // see if we're looking at a builder for a bounded range. the builder is // iteratable since range has these() iterators if (range && range->isNamed("chpl_build_bounded_range")) { // replace the range construction with a direct range iterator Expr* low = range->get(1)->copy(); Expr* high = range->get(2)->copy(); iteratorExpr->replace(new CallExpr("chpl_direct_range_iter", low, high, stride)); } } }
// // Do a breadth first search starting from functions generated for local blocks // for all function calls in each level of the search, if they directly cause // communication, add a local temp that isn't wide. If it is a resolved call, // meaning that it isn't a primitive or external function, clone it and add it // to the queue of functions to handle at the next iteration of the BFS. // static void handleLocalBlocks() { Map<FnSymbol*,FnSymbol*> cache; // cache of localized functions Vec<BlockStmt*> queue; // queue of blocks to localize forv_Vec(BlockStmt, block, gBlockStmts) { if (block->parentSymbol) { // NOAKES 2014/11/25 Transitional. Avoid calling blockInfoGet() if (block->isLoopStmt() == true) { } else if (block->blockInfoGet()) { if (block->blockInfoGet()->isPrimitive(PRIM_BLOCK_LOCAL)) { queue.add(block); } } } } forv_Vec(BlockStmt, block, queue) { std::vector<CallExpr*> calls; collectCallExprs(block, calls); for_vector(CallExpr, call, calls) { localizeCall(call); if (FnSymbol* fn = call->isResolved()) { SET_LINENO(fn); if (FnSymbol* alreadyLocal = cache.get(fn)) { call->baseExpr->replace(new SymExpr(alreadyLocal)); } else { if (!fn->hasFlag(FLAG_EXTERN)) { FnSymbol* local = fn->copy(); local->addFlag(FLAG_LOCAL_FN); local->name = astr("_local_", fn->name); local->cname = astr("_local_", fn->cname); fn->defPoint->insertBefore(new DefExpr(local)); call->baseExpr->replace(new SymExpr(local)); queue.add(local->body); cache.put(fn, local); cache.put(local, local); // to handle recursion if (local->retType->symbol->hasFlag(FLAG_WIDE_REF)) { CallExpr* ret = toCallExpr(local->body->body.tail); INT_ASSERT(ret && ret->isPrimitive(PRIM_RETURN)); // Capture the return expression in a local temp. insertLocalTemp(ret->get(1)); local->retType = ret->get(1)->typeInfo(); } } } } }
static void removeVoidReturn(BlockStmt* cloneBody) { CallExpr* retexpr = toCallExpr(cloneBody->body.tail); INT_ASSERT(retexpr && retexpr->isPrimitive(PRIM_RETURN)); INT_ASSERT(toSymExpr(retexpr->get(1))->symbol() == gVoid); retexpr->remove(); }
void deadVariableElimination(FnSymbol* fn) { Vec<Symbol*> symSet; Vec<SymExpr*> symExprs; collectSymbolSetSymExprVec(fn, symSet, symExprs); Map<Symbol*,Vec<SymExpr*>*> defMap; Map<Symbol*,Vec<SymExpr*>*> useMap; buildDefUseMaps(symSet, symExprs, defMap, useMap); forv_Vec(Symbol, sym, symSet) { // We're interested only in VarSymbols. if (!isVarSymbol(sym)) continue; // A method must have a _this symbol, even if it is not used. if (sym == fn->_this) continue; if (isDeadVariable(sym, defMap, useMap)) { for_defs(se, defMap, sym) { CallExpr* call = toCallExpr(se->parentExpr); INT_ASSERT(call && (call->isPrimitive(PRIM_MOVE) || call->isPrimitive(PRIM_ASSIGN))); Expr* rhs = call->get(2)->remove(); if (!isSymExpr(rhs)) call->replace(rhs); else call->remove(); } sym->defPoint->remove(); } }
void ReturnByRef::insertAssignmentToFormal(FnSymbol* fn, ArgSymbol* formal) { Expr* returnPrim = fn->body->body.tail; SET_LINENO(returnPrim); CallExpr* returnCall = toCallExpr(returnPrim); Expr* returnValue = returnCall->get(1)->remove(); CallExpr* moveExpr = new CallExpr(PRIM_ASSIGN, formal, returnValue); Expr* expr = returnPrim; // Walk backwards while the previous element is an autoDestroy call while (expr->prev != NULL) { bool stop = true; if (CallExpr* call = toCallExpr(expr->prev)) if (FnSymbol* calledFn = call->isResolved()) if (calledFn->hasFlag(FLAG_AUTO_DESTROY_FN)) stop = false; if (stop) break; expr = expr->prev; } Expr* returnOrFirstAutoDestroy = expr; // Add the move to return before the first autoDestroy // At this point we could also invoke some other function // if that turns out to be necessary. It might well be // necessary in order to return array slices by value. returnOrFirstAutoDestroy->insertBefore(moveExpr); }
forv_Vec(FnSymbol, fn, gFnSymbols) { if (VarSymbol* ret = toVarSymbol(fn->getReturnSymbol())) { // The return value of an initCopy function should not be autodestroyed. // Normally, the return value of a function is autoCopied, but since // autoCopy is typically defined in terms of initCopy, this would lead to // infinite recursion. That is, the return value of initCopy must be // handled specially. if (fn->hasFlag(FLAG_INIT_COPY_FN)) ret->removeFlag(FLAG_INSERT_AUTO_DESTROY); // This is just a workaround for memory management being handled specially // for internally reference-counted types. (sandboxing) TypeSymbol* ts = ret->type->symbol; if (ts->hasFlag(FLAG_ARRAY) || ts->hasFlag(FLAG_DOMAIN)) ret->removeFlag(FLAG_INSERT_AUTO_DESTROY); // Do we need to add other record-wrapped types here? Testing will tell. // NOTE 1: When the value of a record field is established in a default // constructor, it is initialized using a MOVE. That means that ownership // of that value is shared between the formal_tmp and the record field. // If the autodestroy flag is left on that formal temp, then it will be // destroyed which -- for ref-counted types -- can result in a dangling // reference. So here, we look for that case and remove it. if (fn->hasFlag(FLAG_DEFAULT_CONSTRUCTOR)) { Map<Symbol*,Vec<SymExpr*>*> defMap; Map<Symbol*,Vec<SymExpr*>*> useMap; buildDefUseMaps(fn, defMap, useMap); std::vector<DefExpr*> defs; collectDefExprs(fn, defs); for_vector(DefExpr, def, defs) { if (VarSymbol* var = toVarSymbol(def->sym)) { // Examine only those bearing the explicit autodestroy flag. if (! var->hasFlag(FLAG_INSERT_AUTO_DESTROY)) continue; // Look for a use in a PRIM_SET_MEMBER where the field is a record // type, and remove the flag. // (We don't actually check that var is of record type, because // chpl__autoDestroy() does nothing when applied to all other types. for_uses(se, useMap, var) { CallExpr* call = toCallExpr(se->parentExpr); if (call->isPrimitive(PRIM_SET_MEMBER) && toSymExpr(call->get(3))->var == var) var->removeFlag(FLAG_INSERT_AUTO_DESTROY); } } } freeDefUseMaps(defMap, useMap); }
// Remove the return statement and the def of 'ret'. Return 'ret'. // See also removeRetSymbolAndUses(). static Symbol* removeParIterReturn(BlockStmt* cloneBody, Symbol* retsym) { CallExpr* retexpr = toCallExpr(cloneBody->body.tail); INT_ASSERT(retexpr && retexpr->isPrimitive(PRIM_RETURN)); if (retsym == NULL) { retsym = toSymExpr(retexpr->get(1))->symbol(); INT_ASSERT(retsym->type->symbol->hasFlag(FLAG_ITERATOR_RECORD)); } else { CallExpr* move = toCallExpr(retsym->getSingleDef()->getStmtExpr()); INT_ASSERT(move->isPrimitive(PRIM_MOVE) || move->isPrimitive(PRIM_ASSIGN)); retsym = toSymExpr(move->get(2))->symbol(); move->remove(); } retexpr->remove(); return retsym; }
void ReturnByRef::insertAssignmentToFormal(FnSymbol* fn, ArgSymbol* formal) { Expr* returnPrim = fn->body->body.tail; SET_LINENO(returnPrim); CallExpr* returnCall = toCallExpr(returnPrim); Expr* returnValue = returnCall->get(1)->remove(); CallExpr* moveExpr = new CallExpr(PRIM_MOVE, formal, returnValue); returnPrim->insertBefore(moveExpr); }
void localizeGlobals() { if (fNoGlobalConstOpt) return; forv_Vec(FnSymbol, fn, gFnSymbols) { Map<Symbol*,VarSymbol*> globals; std::vector<BaseAST*> asts; collect_asts(fn->body, asts); for_vector(BaseAST, ast, asts) { if (SymExpr* se = toSymExpr(ast)) { Symbol* var = se->symbol(); ModuleSymbol* parentmod = toModuleSymbol(var->defPoint->parentSymbol); CallExpr* parentExpr = toCallExpr(se->parentExpr); bool inAddrOf = parentExpr && parentExpr->isPrimitive(PRIM_ADDR_OF); bool lhsOfMove = parentExpr && isMoveOrAssign(parentExpr) && (parentExpr->get(1) == se); // Is var a global constant? // Don't replace the var name in its init function since that's // where we're setting the value. Similarly, dont replace them // inside initStringLiterals // If the parentSymbol is the rootModule, the var is 'void,' // 'false,' '0,' ... // Also don't replace it when it's in an addr of primitive. if (parentmod && fn != parentmod->initFn && fn != initStringLiterals && !inAddrOf && !lhsOfMove && var->hasFlag(FLAG_CONST) && var->defPoint->parentSymbol != rootModule) { VarSymbol* local_global = globals.get(var); SET_LINENO(se); // Set the se line number for output if (!local_global) { const char * newname = astr("local_", var->cname); local_global = newTemp(newname, var->type); fn->insertAtHead(new CallExpr(PRIM_MOVE, local_global, var)); fn->insertAtHead(new DefExpr(local_global)); // Copy string immediates to localized strings so that // we can show the string value in comments next to uses. if (!llvmCodegen) if (VarSymbol* localVarSym = toVarSymbol(var)) if (Immediate* immediate = localVarSym->immediate) if (immediate->const_kind == CONST_KIND_STRING) local_global->immediate = new Immediate(immediate->v_string, immediate->string_kind); globals.put(var, local_global); } se->replace(new SymExpr(toSymbol(local_global))); } } } }
static bool removeIdentityDefs(Symbol* sym) { bool change = false; for_defs(def, defMap, sym) { CallExpr* move = toCallExpr(def->parentExpr); if (move && move->isPrimitive(PRIM_MOVE)) { SymExpr* rhs = toSymExpr(move->get(2)); if (rhs && def->var == rhs->var) { move->remove(); change = true; } } }
static bool removeIdentityDefs(Symbol* sym) { bool change = false; for_defs(def, defMap, sym) { CallExpr* move = toCallExpr(def->parentExpr); if (move && isMoveOrAssign(move)) { SymExpr* rhs = toSymExpr(move->get(2)); if (rhs && def->symbol() == rhs->symbol()) { move->remove(); change = true; } } }
// // Consider a function that takes a formal of type Record by const ref // and that returns that value from the function. The compiler inserts // a PRIM_MOVE operation. // // This work-around inserts an autoCopy to compensate // void ReturnByRef::updateAssignmentsFromRefArgToValue(FnSymbol* fn) { std::vector<CallExpr*> callExprs; collectCallExprs(fn, callExprs); for (size_t i = 0; i < callExprs.size(); i++) { CallExpr* move = callExprs[i]; if (move->isPrimitive(PRIM_MOVE) == true) { SymExpr* lhs = toSymExpr(move->get(1)); SymExpr* rhs = toSymExpr(move->get(2)); if (lhs != NULL && rhs != NULL) { VarSymbol* symLhs = toVarSymbol(lhs->symbol()); ArgSymbol* symRhs = toArgSymbol(rhs->symbol()); if (symLhs != NULL && symRhs != NULL) { if (isUserDefinedRecord(symLhs->type) == true && symRhs->type == symLhs->type) { if (symLhs->hasFlag(FLAG_ARG_THIS) == false && (symRhs->intent == INTENT_REF || symRhs->intent == INTENT_CONST_REF)) { SET_LINENO(move); CallExpr* autoCopy = NULL; rhs->remove(); autoCopy = new CallExpr(autoCopyMap.get(symRhs->type), rhs); move->insertAtTail(autoCopy); } } } } } } }
void ReturnByRef::updateAssignmentsFromRefTypeToValue(FnSymbol* fn) { std::vector<CallExpr*> callExprs; collectCallExprs(fn, callExprs); Map<Symbol*,Vec<SymExpr*>*> defMap; Map<Symbol*,Vec<SymExpr*>*> useMap; buildDefUseMaps(fn, defMap, useMap); for (size_t i = 0; i < callExprs.size(); i++) { CallExpr* move = callExprs[i]; if (move->isPrimitive(PRIM_MOVE) == true) { SymExpr* symLhs = toSymExpr (move->get(1)); CallExpr* callRhs = toCallExpr(move->get(2)); if (symLhs && callRhs && callRhs->isPrimitive(PRIM_DEREF)) { VarSymbol* varLhs = toVarSymbol(symLhs->symbol()); SymExpr* symRhs = toSymExpr(callRhs->get(1)); VarSymbol* varRhs = toVarSymbol(symRhs->symbol()); // MPF 2016-10-02: It seems to me that this code should also handle the // case that symRhs is an ArgSymbol, but adding that caused problems // in the handling of out argument intents. if (varLhs != NULL && varRhs != NULL) { if (isUserDefinedRecord(varLhs->type) == true && varRhs->type == varLhs->type->refType) { // HARSHBARGER 2015-12-11: // `init_untyped_var` in the `normalize` pass may insert an // initCopy, which means that we should not insert an autocopy // for that same variable. bool initCopied = false; for_uses(use, useMap, varLhs) { if (CallExpr* call = toCallExpr(use->parentExpr)) { if (FnSymbol* parentFn = call->isResolved()) { if (parentFn->hasFlag(FLAG_INIT_COPY_FN)) { initCopied = true; break; } } } } if (!initCopied) { SET_LINENO(move); SymExpr* lhsCopy0 = symLhs->copy(); SymExpr* lhsCopy1 = symLhs->copy(); FnSymbol* autoCopy = autoCopyMap.get(varLhs->type); CallExpr* copyExpr = new CallExpr(autoCopy, lhsCopy0); CallExpr* moveExpr = new CallExpr(PRIM_MOVE,lhsCopy1, copyExpr); move->insertAfter(moveExpr); } } } }
// Returns true if the symbol is read in the containing expression, // false otherwise. If the operand is used as an address, // that does not count as a 'read', so false is returned in that case. static bool isUse(SymExpr* se) { if (toGotoStmt(se->parentExpr)) return false; if (toCondStmt(se->parentExpr)) return true; if (toBlockStmt(se->parentExpr)) return true; if (isDefExpr(se->parentExpr)) return false; CallExpr* call = toCallExpr(se->parentExpr); if (FnSymbol* fn = call->resolvedFunction()) { // Skip the "base" symbol. if (se->symbol() == fn) return false; // A "normal" call. ArgSymbol* arg = actual_to_formal(se); if (arg->intent == INTENT_OUT || (arg->intent & INTENT_FLAG_REF)) return false; } else { INT_ASSERT(call->primitive); const bool isFirstActual = call->get(1) == se; switch(call->primitive->tag) { default: return true; case PRIM_MOVE: case PRIM_ASSIGN: case PRIM_ADD_ASSIGN: case PRIM_SUBTRACT_ASSIGN: case PRIM_MULT_ASSIGN: case PRIM_DIV_ASSIGN: case PRIM_MOD_ASSIGN: case PRIM_LSH_ASSIGN: case PRIM_RSH_ASSIGN: case PRIM_AND_ASSIGN: case PRIM_OR_ASSIGN: case PRIM_XOR_ASSIGN: if (isFirstActual) { return false; } return true; case PRIM_ADDR_OF: case PRIM_SET_REFERENCE: return false; // See Note #2. case PRIM_PRIVATE_BROADCAST: // The operand is used by name (it must be a manifest constant). // Thus it acts more like an address than a value. return false; case PRIM_CHPL_COMM_GET: case PRIM_CHPL_COMM_PUT: case PRIM_CHPL_COMM_ARRAY_GET: case PRIM_CHPL_COMM_ARRAY_PUT: case PRIM_CHPL_COMM_GET_STRD: case PRIM_CHPL_COMM_PUT_STRD: // ('comm_get/put' locAddr locale widePtr len) // The first and third operands are treated as addresses. // The second and fourth are values if (se == call->get(2) || se == call->get(4)) { return true; } return false; case PRIM_CHPL_COMM_REMOTE_PREFETCH: // comm prefetch locale widePtr len // second argument is an address // first and third are values. if (isFirstActual || se == call->get(3)) { return true; } return false; case PRIM_SET_MEMBER: // The first operand works like a reference, and the second is a field // name. Only the third is a replaceable use. if (se == call->get(3)) { return true; } return false; case PRIM_GET_MEMBER: case PRIM_GET_MEMBER_VALUE: if (isFirstActual) { return false; } return true; case PRIM_ARRAY_SET: case PRIM_ARRAY_SET_FIRST: case PRIM_ARRAY_GET: case PRIM_ARRAY_GET_VALUE: // The first operand is treated like a reference. if (isFirstActual) { return false; } return true; case PRIM_SET_UNION_ID: // The first operand is treated like a reference. if (isFirstActual) { return false; } return true; } } return true; }
// // Attempts to replace references with the variables the references point to, // provided the references have a single definition. // // For example: // var foo : int; // ref A : int; // (move A (addr-of foo)) // // (move B (deref A)) ---> (move B foo) // void eliminateSingleAssignmentReference(Map<Symbol*,Vec<SymExpr*>*>& defMap, Map<Symbol*,Vec<SymExpr*>*>& useMap, Symbol* var) { if (CallExpr* move = findRefDef(defMap, var)) { if (CallExpr* rhs = toCallExpr(move->get(2))) { if (rhs->isPrimitive(PRIM_ADDR_OF) || rhs->isPrimitive(PRIM_SET_REFERENCE)) { bool stillAlive = false; for_uses(se, useMap, var) { CallExpr* parent = toCallExpr(se->parentExpr); SET_LINENO(se); if (parent && (parent->isPrimitive(PRIM_DEREF) || isDerefMove(parent))) { SymExpr* se = toSymExpr(rhs->get(1)->copy()); INT_ASSERT(se); Expr* toReplace = parent; if (isMoveOrAssign(parent)) { toReplace = parent->get(2); } toReplace->replace(se); ++s_ref_repl_count; addUse(useMap, se); } else if (parent && (parent->isPrimitive(PRIM_GET_MEMBER_VALUE) || parent->isPrimitive(PRIM_GET_MEMBER) || parent->isPrimitive(PRIM_GET_MEMBER_VALUE) || parent->isPrimitive(PRIM_GET_MEMBER))) { SymExpr* se = toSymExpr(rhs->get(1)->copy()); INT_ASSERT(se); parent->get(1)->replace(se); ++s_ref_repl_count; addUse(useMap, se); } else if (parent && (parent->isPrimitive(PRIM_MOVE) || parent->isPrimitive(PRIM_SET_REFERENCE))) { CallExpr* rhsCopy = rhs->copy(); if (parent->isPrimitive(PRIM_SET_REFERENCE)) { // Essentially a pointer copy like a (move refA refB) parent = toCallExpr(parent->parentExpr); INT_ASSERT(parent && isMoveOrAssign(parent)); } parent->get(2)->replace(rhsCopy); ++s_ref_repl_count; SymExpr* se = toSymExpr(rhsCopy->get(1)); INT_ASSERT(se); addUse(useMap, se); // BHARSH TODO: Is it possible to handle the following case safely // for PRIM_ASSIGN? // // ref i_foo : T; // (move i_foo (set reference bar)) // (= call_tmp i_foo) // // Should that turn into (= call_tmp bar)? } else if (parent && parent->isPrimitive(PRIM_ASSIGN) && parent->get(1) == se) { // for_defs should handle this case } else if (parent && parent->isResolved()) { stillAlive = true; // TODO -- a reference argument can be passed directly } else { stillAlive = true; } } for_defs(se, defMap, var) { CallExpr* parent = toCallExpr(se->parentExpr); SET_LINENO(se); if (parent == move) continue; if (parent && isMoveOrAssign(parent)) { SymExpr* se = toSymExpr(rhs->get(1)->copy()); INT_ASSERT(se); parent->get(1)->replace(se); ++s_ref_repl_count; addDef(defMap, se); } else stillAlive = true; } if (!stillAlive) { var->defPoint->remove(); Vec<SymExpr*>* defs = defMap.get(var); if (defs == NULL) { INT_FATAL(var, "Expected var to be defined"); } // Remove the first definition from the AST. defs->v[0]->getStmtExpr()->remove(); } } else if (rhs->isPrimitive(PRIM_GET_MEMBER) ||
BlockStmt* ParamForLoop::buildParamForLoop(VarSymbol* indexVar, Expr* range, BlockStmt* stmts) { VarSymbol* lowVar = newParamVar(); VarSymbol* highVar = newParamVar(); VarSymbol* strideVar = newParamVar(); LabelSymbol* breakLabel = new LabelSymbol("_breakLabel"); LabelSymbol* continueLabel = new LabelSymbol("_unused_continueLabel"); CallExpr* call = toCallExpr(range); Expr* low = NULL; Expr* high = NULL; Expr* stride = NULL; BlockStmt* outer = new BlockStmt(); if (call && call->isNamed("chpl_by")) { stride = call->get(2)->remove(); call = toCallExpr(call->get(1)); } else { stride = new SymExpr(new_IntSymbol(1)); } if (call && call->isNamed("chpl_build_bounded_range")) { low = call->get(1)->remove(); high = call->get(1)->remove(); } else { USR_FATAL(range, "iterators for param-for-loops must be bounded literal ranges"); } outer->insertAtTail(new DefExpr(indexVar, new_IntSymbol((int64_t) 0))); outer->insertAtTail(new DefExpr(lowVar)); outer->insertAtTail(new CallExpr(PRIM_MOVE, lowVar, low)); outer->insertAtTail(new DefExpr(highVar)); outer->insertAtTail(new CallExpr(PRIM_MOVE, highVar, high)); outer->insertAtTail(new DefExpr(strideVar)); outer->insertAtTail(new CallExpr(PRIM_MOVE, strideVar, stride)); outer->insertAtTail(new ParamForLoop(indexVar, lowVar, highVar, strideVar, continueLabel, breakLabel, stmts)); // this continueLabel will be replaced by a per-iteration one. outer->insertAtTail(new DefExpr(continueLabel)); outer->insertAtTail(new DefExpr(breakLabel)); return buildChapelStmt(outer); }
static bool inferRefToConst(Symbol* sym) { INT_ASSERT(sym->isRef()); bool isConstRef = sym->qualType().getQual() == QUAL_CONST_REF; const bool wasRefToConst = sym->hasFlag(FLAG_REF_TO_CONST); ConstInfo* info = infoMap[sym]; // If this ref isn't const, then it can't point to a const thing if (info == NULL) { return false; } else if (info->finalizedRefToConst || wasRefToConst || !isConstRef) { return wasRefToConst; } bool isFirstCall = false; if (info->alreadyCalled == false) { isFirstCall = true; info->alreadyCalled = true; } bool isRefToConst = true; if (isArgSymbol(sym)) { // Check each call and set isRefToConst to false if any actual is not a ref // to a const. FnSymbol* fn = toFnSymbol(sym->defPoint->parentSymbol); if (fn->hasFlag(FLAG_VIRTUAL) || fn->hasFlag(FLAG_EXPORT) || fn->hasFlag(FLAG_EXTERN)) { // Not sure how to best handle virtual calls, so simply set false for now // // For export or extern functions, return false because we don't have // all the information about how the function is called. isRefToConst = false; } else { // Need this part to be re-entrant in case of recursive functions while (info->fnUses != NULL && isRefToConst) { SymExpr* se = info->fnUses; info->fnUses = se->symbolSymExprsNext; CallExpr* call = toCallExpr(se->parentExpr); INT_ASSERT(call && call->isResolved()); Symbol* actual = toSymExpr(formal_to_actual(call, sym))->symbol(); if (actual->isRef()) { // I don't think we technically need to skip if the actual is the // same symbol as the formal, but it makes things simpler. if (actual != sym && !inferRefToConst(actual)) { isRefToConst = false; } } else { // Passing a non-ref actual to a reference formal is currently // considered to be the same as an addr-of if (actual->qualType().getQual() != QUAL_CONST_VAL) { isRefToConst = false; } } } } } while (info->hasMore() && isRefToConst) { SymExpr* use = info->next(); CallExpr* call = toCallExpr(use->parentExpr); if (call == NULL) continue; if (isMoveOrAssign(call)) { if (use == call->get(1)) { if (SymExpr* se = toSymExpr(call->get(2))) { if (se->isRef() && !inferRefToConst(se->symbol())) { isRefToConst = false; } } else { CallExpr* RHS = toCallExpr(call->get(2)); INT_ASSERT(RHS); if (RHS->isPrimitive(PRIM_ADDR_OF) || RHS->isPrimitive(PRIM_SET_REFERENCE)) { SymExpr* src = toSymExpr(RHS->get(1)); if (src->isRef()) { if (!inferRefToConst(src->symbol())) { isRefToConst = false; } } else { if (src->symbol()->qualType().getQual() != QUAL_CONST_VAL) { isRefToConst = false; } } } else { isRefToConst = false; } } } } else if (call->isResolved()) { isRefToConst = true; } else { isRefToConst = isSafeRefPrimitive(use); } } if (isFirstCall) { if (isRefToConst) { INT_ASSERT(info->finalizedRefToConst == false); sym->addFlag(FLAG_REF_TO_CONST); } info->reset(); info->finalizedRefToConst = true; } else if (!isRefToConst) { info->finalizedRefToConst = true; } return isRefToConst; }
// Note: This function is currently not recursive static bool inferConst(Symbol* sym) { INT_ASSERT(!sym->isRef()); const bool wasConstVal = sym->qualType().getQual() == QUAL_CONST_VAL; ConstInfo* info = infoMap[sym]; // 'info' may be null if the argument is never used. In that case we can // consider 'sym' to be a const-ref. By letting the rest of the function // proceed, we'll fix up the qualifier for such symbols at the end. if (info == NULL) { return true; } else if (info->finalizedConstness || wasConstVal) { return wasConstVal; } bool isConstVal = true; int numDefs = 0; while (info->hasMore() && isConstVal) { SymExpr* use = info->next(); CallExpr* call = toCallExpr(use->parentExpr); if (call == NULL) { // Could be a DefExpr, or the condition for a while loop. // BHARSH: I'm not sure of all the possibilities continue; } CallExpr* parent = toCallExpr(call->parentExpr); if (call->isResolved()) { ArgSymbol* form = actual_to_formal(use); // // If 'sym' is constructed through a _retArg, we can consider that to // be a single 'def'. // if (form->hasFlag(FLAG_RETARG)) { numDefs += 1; } else if (form->isRef()) { if (!inferConstRef(form)) { isConstVal = false; } } } else if (parent && isMoveOrAssign(parent)) { if (call->isPrimitive(PRIM_ADDR_OF) || call->isPrimitive(PRIM_SET_REFERENCE)) { Symbol* LHS = toSymExpr(parent->get(1))->symbol(); INT_ASSERT(LHS->isRef()); if (onlyUsedForRetarg(LHS, parent)) { numDefs += 1; } else if (!inferConstRef(LHS)) { isConstVal = false; } } } else if (isMoveOrAssign(call)) { if (use == call->get(1)) { numDefs += 1; } } else { // To be safe, exit the loop with 'false' if we're unsure of how to // handle a primitive. isConstVal = false; } if (numDefs > 1) { isConstVal = false; } } if (isConstVal && !info->finalizedConstness) { if (ArgSymbol* arg = toArgSymbol(sym)) { INT_ASSERT(arg->intent & INTENT_FLAG_IN); arg->intent = INTENT_CONST_IN; } else { INT_ASSERT(isVarSymbol(sym)); sym->qual = QUAL_CONST_VAL; } } info->reset(); info->finalizedConstness = true; return isConstVal; }
// // Returns 'true' if 'sym' is (or should be) a const-ref. // If 'sym' can be a const-ref, but is not, this function will change either // the intent or qual of the Symbol to const-ref. // static bool inferConstRef(Symbol* sym) { INT_ASSERT(sym->isRef()); bool wasConstRef = sym->qualType().getQual() == QUAL_CONST_REF; if (sym->defPoint->parentSymbol->hasFlag(FLAG_EXTERN)) { return wasConstRef; } ConstInfo* info = infoMap[sym]; // 'info' may be null if the argument is never used. In that case we can // consider 'sym' to be a const-ref. By letting the rest of the function // proceed, we'll fix up the qualifier for such symbols at the end. if (info == NULL) { return true; } else if (info->finalizedConstness || wasConstRef) { return wasConstRef; } bool isFirstCall = false; if (info->alreadyCalled == false) { isFirstCall = true; info->alreadyCalled = true; } bool isConstRef = true; while (info->hasMore() && isConstRef) { SymExpr* use = info->next(); CallExpr* call = toCallExpr(use->parentExpr); INT_ASSERT(call); CallExpr* parent = toCallExpr(call->parentExpr); if (call->isResolved()) { ArgSymbol* form = actual_to_formal(use); if (form->isRef() && !inferConstRef(form)) { isConstRef = false; } } else if (parent && isMoveOrAssign(parent)) { if (!canRHSBeConstRef(parent, use)) { isConstRef = false; } } else if (call->isPrimitive(PRIM_MOVE)) { // // Handles three cases: // 1) MOVE use value - writing to a reference, so 'use' cannot be const // 2) MOVE ref use - if the LHS is not const, use cannot be const either // 3) MOVE value use - a dereference of 'use' // if (use == call->get(1)) { // CASE 1 if (!call->get(2)->isRef()) { isConstRef = false; } } else { // 'use' is the RHS of a MOVE if (call->get(1)->isRef()) { // CASE 2 SymExpr* se = toSymExpr(call->get(1)); INT_ASSERT(se); if (!inferConstRef(se->symbol())) { isConstRef = false; } } // else CASE 3: do nothing because isConstRef is already true } } else if (call->isPrimitive(PRIM_ASSIGN)) { if (use == call->get(1)) { isConstRef = false; } } else if (call->isPrimitive(PRIM_SET_MEMBER) || call->isPrimitive(PRIM_SET_SVEC_MEMBER)) { // BHARSH 2016-11-02 // In the expr (set_member base member rhs), // If use == base, I take the conservative approach and decide that 'use' // is not a const-ref. I'm not sure that we've decided what const means // for fields yet, so this seems safest. // // If use == rhs, then we would need to do analysis for the member field. // That's beyond the scope of what I'm attempting at the moment, so to // be safe we'll return false for that case. if (use == call->get(1) || use == call->get(3)) { isConstRef = false; } else { // use == member // If 'rhs' is not a ref, then we're writing into 'use'. Otherwise it's // a pointer copy and we don't care if 'rhs' is writable. if (!call->get(3)->isRef()) { isConstRef = false; } } } else { // To be safe, exit the loop with 'false' if we're unsure of how to // handle a primitive. isConstRef = false; } } if (isFirstCall) { if (isConstRef) { INT_ASSERT(info->finalizedConstness == false); if (ArgSymbol* arg = toArgSymbol(sym)) { arg->intent = INTENT_CONST_REF; } else { INT_ASSERT(isVarSymbol(sym)); sym->qual = QUAL_CONST_REF; } } info->reset(); info->finalizedConstness = true; } else if (!isConstRef) { info->finalizedConstness = true; } return isConstRef; }
/* * Attempts to replace iteration over simple anonymous ranges with calls to * direct iterators that take low, high and stride as arguments. This is to * avoid the cost of constructing ranges, and if the stride is known at compile * time, provide a more optimized iterator that uses "<, <=, >, or >=" as the * relational operator. * * This is only meant to replace anonymous range iteration for "simple" ranges. * Simple means it's a range of the form "low..high", "low..high by stride", or * "low..#count". Anything more complex is ignored with the thinking that this * should optimize the most common range iterators, but it could be expanded to * handle more cases. * * An alternative is to update scalar replacement of aggregates to work on * ranges, which should be able to achieve similar results as this optimization * while handling all ranges, including non-anonymous ranges. * * This function will optimize things like: * - "for i in 1..10" * - "for i in 1..10+1" * - "var lo=1, hi=10;for i in lo..hi" * - "for i in 1..10 by 2" * - "for i in 1..#10" * - "for (i, j) in zip(1..10 by 2, 1..10 by -2)" * - "for (i, j) in zip(A, 1..10 by 2)" // will optimize range iter still * - "coforall i in 1..10 by 2" // works for coforalls as well * * Will not optimize ranges like: * - "for in in (1..)" // doesn't handle unbounded ranges * - "for i in 1..10 by 2 by 2" // doesn't handle more than one by operator * - "for i in 1..10 align 2" // doesn't handle align operator * - "for i in (1..10)#2" // doesn't handle bounded counted ranges * - "for i in 1..#10 by 2" // doesn't handle strided and counted ranges * - "var r = 1..10"; for i in r" // not an anonymous range * - "forall i in 1..10" // doesn't get applied to foralls * * Note that this function is pretty fragile because it relies on names of * functions/iterators as well as the arguments and order of those * functions/iterators but there's not really a way around it this early in * compilation. If the iterator can't be replaced, it is left unchanged. */ static void tryToReplaceWithDirectRangeIterator(Expr* iteratorExpr) { if (CallExpr* call = toCallExpr(iteratorExpr)) { CallExpr* range = NULL; Expr* stride = NULL; Expr* count = NULL; // grab the stride if we have a strided range if (call->isNamed("chpl_by")) { range = toCallExpr(call->get(1)->copy()); stride = toExpr(call->get(2)->copy()); } // or grab the count if we have a counted range and set unit stride else if (call->isNamed("#")) { range = toCallExpr(call->get(1)->copy()); count = toExpr(call->get(2)->copy()); stride = new SymExpr(new_IntSymbol(1)); } // or assume the call is the range (checked below) and set unit stride else { range = call; stride = new SymExpr(new_IntSymbol(1)); } // // see if we're looking at a range builder. The builder is iteratable since // range has these() iterators // // replace fully bounded (and possibly strided range) with a direct range // iter. e.g. replace: // // `low..high by stride` // // with: // // `chpl_direct_range_iter(low, high, stride)` if (range && range->isNamed("chpl_build_bounded_range")) { // replace the range construction with a direct range iterator Expr* low = range->get(1)->copy(); Expr* high = range->get(2)->copy(); iteratorExpr->replace(new CallExpr("chpl_direct_range_iter", low, high, stride)); } // replace a counted, low bounded range with unit stride with an equivalent // direct range iter. e.g. replace: // // `low..#count` (which is equivalent to `low..low+count-1`) // // with: // // `chpl_direct_range_iter(low, low+count-1, 1)` else if (count && range && range->isNamed("chpl_build_low_bounded_range")) { Expr* low = range->get(1)->copy(); Expr* high = new CallExpr("-", new CallExpr("+", low->copy(), count), new_IntSymbol(1)); iteratorExpr->replace(new CallExpr("chpl_direct_range_iter", low, high, stride)); } } }
// This routine returns true if the value of the given symbol may have changed // due to execution of the containing expression. // If the symbol is a reference, this means that the address to which the // symbol points will be changed, not the value contained in that address. See // isRefUse() for that case. // To be conservative, the routine should return true by default and then // select the cases where we are sure nothing has changed. static bool needsKilling(SymExpr* se, std::set<Symbol*>& liveRefs) { INT_ASSERT(se->isRef() == false); if (toGotoStmt(se->parentExpr)) { return false; } if (toCondStmt(se->parentExpr)) { return false; } if (toBlockStmt(se->parentExpr)) { return false; } if (isDefExpr(se->parentExpr)) { return false; } CallExpr* call = toCallExpr(se->parentExpr); if (FnSymbol* fn = call->resolvedFunction()) { // Skip the "base" symbol. if (se->symbol() == fn) { return false; } ArgSymbol* arg = actual_to_formal(se); if (arg->intent == INTENT_OUT || arg->intent == INTENT_INOUT || arg->intent == INTENT_REF || arg->hasFlag(FLAG_ARG_THIS)) // Todo: replace with arg intent check? { liveRefs.insert(se->symbol()); return true; } if (isRecordWrappedType(arg->type)) { return true; } return false; } else { const bool isFirstActual = call->get(1) == se; if ((call->isPrimitive(PRIM_MOVE) || call->isPrimitive(PRIM_ASSIGN)) && isFirstActual) { return true; } if (isOpEqualPrim(call) && isFirstActual) { return true; } if (call->isPrimitive(PRIM_SET_MEMBER) && isFirstActual) { return true; } if (call->isPrimitive(PRIM_ARRAY_SET) || call->isPrimitive(PRIM_ARRAY_SET_FIRST)) { if (isFirstActual) { return true; } return false; } if (call->isPrimitive(PRIM_GET_MEMBER)) { // This creates an alias to a portion of the first arg. // We could track this as a reference and invalidate a pair containing // this symbol when the ref is dereferenced. But for now, we want to // preserve the mapping ref = &value in the RefMap, so having a (ref, // value) pair also possibly mean ref = &(value.subfield) does not quite // fit. // We could keep the ability to do (deref ref) <- value substitution by // keeping a separate map for "true" references, or by performing those // substitutions in a separate pass. // For now, we treat subfield extraction as evidence of a future change // to the symbol itself, and use that fact to remove it from // consideration in copy propagation. if (isFirstActual) { // We select just the case where the referent is passed by value, // because in the other case, the address of the object is not // returned, so that means that the address (i.e. the value of the // reference variable) does not change. return true; } return false; } if (call->isPrimitive(PRIM_ADDR_OF) || call->isPrimitive(PRIM_SET_REFERENCE)) { liveRefs.insert(se->symbol()); return true; } return false; } INT_ASSERT(0); // Should never get here. return true; }