From 858be153eb895fa5391da13746c0195929fc5fbc Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Sat, 13 Dec 2014 09:46:22 -0600 Subject: [PATCH 01/53] Remove returnRecordsByRefArguments and all autodestroy calls. This results in 211 regressions. --- compiler/AST/build.cpp | 8 ++++++++ compiler/passes/normalize.cpp | 7 +++++++ compiler/passes/scopeResolve.cpp | 3 ++- compiler/resolution/callDestructors.cpp | 17 ++++++++++++----- compiler/resolution/cullOverReferences.cpp | 5 +++++ compiler/resolution/functionResolution.cpp | 13 ++++++++++++- compiler/resolution/implementForallIntents.cpp | 2 ++ compiler/resolution/wrappers.cpp | 2 ++ 8 files changed, 50 insertions(+), 7 deletions(-) diff --git a/compiler/AST/build.cpp b/compiler/AST/build.cpp index 904c063cfc05..87d86ee7ea5e 100644 --- a/compiler/AST/build.cpp +++ b/compiler/AST/build.cpp @@ -585,13 +585,17 @@ destructureIndices(BlockStmt* block, var->addFlag(FLAG_INDEX_VAR); if (coforall) var->addFlag(FLAG_COFORALL_INDEX_VAR); +#ifndef HILDE_MM var->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif } else if (SymExpr* sym = toSymExpr(indices)) { block->insertAtHead(new CallExpr(PRIM_MOVE, sym->var, init)); sym->var->addFlag(FLAG_INDEX_VAR); if (coforall) sym->var->addFlag(FLAG_COFORALL_INDEX_VAR); +#ifndef HILDE_MM sym->var->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif } } @@ -979,7 +983,9 @@ buildForallLoopStmt(Expr* indices, followIdx->addFlag(FLAG_INDEX_OF_INTEREST); leadIdxCopy->addFlag(FLAG_INDEX_VAR); +#ifndef HILDE_MM leadIdxCopy->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif followIdx->addFlag(FLAG_INDEX_OF_INTEREST); // ForallLeaderArgs: stash references so we know where things are. @@ -1362,7 +1368,9 @@ CallExpr* buildReduceExpr(Expr* opExpr, Expr* dataExpr, bool zippered) { VarSymbol* localOp = newTemp(); leadIdxCopy->addFlag(FLAG_INDEX_VAR); +#ifndef HILDE_MM leadIdxCopy->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif ForLoop* followBody = new ForLoop(followIdx, followIter, NULL); diff --git a/compiler/passes/normalize.cpp b/compiler/passes/normalize.cpp index 7cde54eedbec..0e9f2072c0c5 100644 --- a/compiler/passes/normalize.cpp +++ b/compiler/passes/normalize.cpp @@ -488,7 +488,9 @@ static void insertRetMove(FnSymbol* fn, VarSymbol* retval, CallExpr* ret) { // copy to the return value variable. // Contrast this with a move, which merely shares ownership between the // bitwise copies of the object. +#ifndef HILDE_MM retval->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif } else if (!fn->hasFlag(FLAG_WRAPPER) && strcmp(fn->name, "iteratorIndex") && strcmp(fn->name, "iteratorIndexHelp")) @@ -829,8 +831,11 @@ static void insert_call_temps(CallExpr* call) VarSymbol* tmp = newTemp("call_tmp"); if (!parentCall || !parentCall->isNamed("chpl__initCopy")) tmp->addFlag(FLAG_EXPR_TEMP); +#ifndef HILDE_MM + // Can't we figure this out later? if (call->isPrimitive(PRIM_NEW)) tmp->addFlag(FLAG_INSERT_AUTO_DESTROY_FOR_EXPLICIT_NEW); +#endif if (call->isPrimitive(PRIM_TYPEOF)) tmp->addFlag(FLAG_TYPE_VARIABLE); tmp->addFlag(FLAG_MAYBE_PARAM); @@ -857,6 +862,7 @@ fix_def_expr(VarSymbol* var) { if (!type && !init) return; // already fixed +#ifndef HILDE_MM // // add "insert auto destroy" pragma to user variables that should be // auto destroyed @@ -873,6 +879,7 @@ fix_def_expr(VarSymbol* var) { fn->_this != var && // Note 2. !fn->hasFlag(FLAG_TYPE_CONSTRUCTOR)) var->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif // // handle "no copy" variables diff --git a/compiler/passes/scopeResolve.cpp b/compiler/passes/scopeResolve.cpp index edbf272dcfe8..89fdc47f78dc 100644 --- a/compiler/passes/scopeResolve.cpp +++ b/compiler/passes/scopeResolve.cpp @@ -887,8 +887,9 @@ static void build_constructor(AggregateType* ct) { if (!field->hasFlag(FLAG_TYPE_VARIABLE) && !exprType) { // init && !exprType VarSymbol* tmp = newTemp(); - +#ifndef HILDE_MM tmp->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif tmp->addFlag(FLAG_MAYBE_PARAM); tmp->addFlag(FLAG_MAYBE_TYPE); diff --git a/compiler/resolution/callDestructors.cpp b/compiler/resolution/callDestructors.cpp index a48cd9390c2e..5b797f75334c 100644 --- a/compiler/resolution/callDestructors.cpp +++ b/compiler/resolution/callDestructors.cpp @@ -17,6 +17,8 @@ * limitations under the License. */ +#define HILDE_MM 1 + #include "passes.h" #include "astutil.h" @@ -28,6 +30,7 @@ #include "symbol.h" +#ifndef HILDE_MM // Clear autoDestroy flags on variables that get assigned to the return value of // certain functions. // @@ -663,7 +666,7 @@ returnRecordsByReferenceArguments() { } freeDefUseMaps(defMap, useMap); } - +#endif static void fixupDestructors() { @@ -758,7 +761,7 @@ fixupDestructors() { } } - +#ifndef HILDE_MM static void insertGlobalAutoDestroyCalls() { const char* name = "chpl__autoDestroyGlobals"; SET_LINENO(baseModule); @@ -779,7 +782,7 @@ static void insertGlobalAutoDestroyCalls() { } fn->insertAtTail(new CallExpr(PRIM_RETURN, gVoid)); } - +#endif static void insertDestructorCalls() { @@ -796,7 +799,7 @@ static void insertDestructorCalls() } } - +#ifndef HILDE_MM static void insertAutoCopyTemps() { Map*> defMap; @@ -827,7 +830,7 @@ static void insertAutoCopyTemps() freeDefUseMaps(defMap, useMap); } - +#endif // This routine inserts autoCopy calls ahead of yield statements as necessary, // so the calling routine "owns" the returned value. @@ -909,12 +912,16 @@ void callDestructors() { fixupDestructors(); insertDestructorCalls(); +#ifndef HILDE_MM insertAutoCopyTemps(); cullAutoDestroyFlags(); cullExplicitAutoDestroyFlags(); insertAutoDestroyCalls(); returnRecordsByReferenceArguments(); +#endif insertYieldTemps(); +#ifndef HILDE_MM insertGlobalAutoDestroyCalls(); +#endif insertReferenceTemps(); } diff --git a/compiler/resolution/cullOverReferences.cpp b/compiler/resolution/cullOverReferences.cpp index 4647a2a4791b..d99c4bfd22d6 100644 --- a/compiler/resolution/cullOverReferences.cpp +++ b/compiler/resolution/cullOverReferences.cpp @@ -109,10 +109,15 @@ void cullOverReferences() { base->var = copy; VarSymbol* tmp = newTemp(copy->retType); move->insertBefore(new DefExpr(tmp)); +#ifndef HILDE_MM + // Why is this here? + // Marking variables for autocopy/autodestroy ought to be done in + // one place once and for all. Is this that place? if (requiresImplicitDestroy(call)) { tmp->addFlag(FLAG_INSERT_AUTO_COPY); tmp->addFlag(FLAG_INSERT_AUTO_DESTROY); } +#endif if (useMap.get(se->var) && useMap.get(se->var)->n > 0) { move->insertAfter(new CallExpr(PRIM_MOVE, se->var, new CallExpr(PRIM_ADDR_OF, tmp))); diff --git a/compiler/resolution/functionResolution.cpp b/compiler/resolution/functionResolution.cpp index cab9de32ae12..6fb350719dfb 100644 --- a/compiler/resolution/functionResolution.cpp +++ b/compiler/resolution/functionResolution.cpp @@ -232,7 +232,9 @@ Map iteratorFollowerMap; // iterator->leader map for promot //# Static Function Declarations //# static bool hasRefField(Type *type); +#ifndef HILDE_MM static bool typeHasRefField(Type *type); +#endif static FnSymbol* resolveUninsertedCall(Type* type, CallExpr* call); static void makeRefType(Type* type); static void resolveAutoCopy(Type* type); @@ -424,6 +426,7 @@ static bool hasRefField(Type *type) { return true; } +#ifndef HILDE_MM static bool typeHasRefField(Type *type) { if (AggregateType *ct = toAggregateType(type)) { for_fields(field, ct) { @@ -432,6 +435,7 @@ static bool typeHasRefField(Type *type) { } return false; } +#endif // // build reference type @@ -3771,7 +3775,9 @@ static void addLocalCopiesAndWritebacks(FnSymbol* fn, SymbolMap& formals2vars) !isRefCountedType(formalType)) { tmp->addFlag(FLAG_CONST); +#ifndef HILDE_MM tmp->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif } // This switch adds the extra code inside the current function necessary @@ -3815,7 +3821,9 @@ static void addLocalCopiesAndWritebacks(FnSymbol* fn, SymbolMap& formals2vars) // If the compiler verifies in a separate pass that it is never written, // we don't have to copy it. fn->insertAtHead(new CallExpr(PRIM_MOVE, tmp, new CallExpr("chpl__initCopy", formal))); +#ifndef HILDE_MM tmp->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif break; case INTENT_BLANK: @@ -3840,6 +3848,7 @@ static void addLocalCopiesAndWritebacks(FnSymbol* fn, SymbolMap& formals2vars) // intentional: It gives tuple-wrapped record-wrapped types different // behavior from bare record-wrapped types. fn->insertAtHead(new CallExpr(PRIM_MOVE, tmp, new CallExpr("chpl__autoCopy", formal))); +#ifndef HILDE_MM // WORKAROUND: // This is a temporary bug fix that results in leaked memory. // @@ -3868,6 +3877,7 @@ static void addLocalCopiesAndWritebacks(FnSymbol* fn, SymbolMap& formals2vars) (formal->type->getModule()->modTag==MOD_STANDARD))) || !typeHasRefField(formal->type)) tmp->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif } } else { @@ -5467,6 +5477,7 @@ postFold(Expr* expr) { } } if (!set) { +#ifndef HILDE_MM if (lhs->var->hasFlag(FLAG_EXPR_TEMP) && !lhs->var->hasFlag(FLAG_TYPE_VARIABLE)) { if (CallExpr* rhsCall = toCallExpr(call->get(2))) { @@ -5476,7 +5487,7 @@ postFold(Expr* expr) { } } } - +#endif if (isReferenceType(lhs->var->type) || lhs->var->type->symbol->hasFlag(FLAG_REF_ITERATOR_CLASS) || lhs->var->type->symbol->hasFlag(FLAG_ARRAY)) diff --git a/compiler/resolution/implementForallIntents.cpp b/compiler/resolution/implementForallIntents.cpp index e63eaca09152..d6e804917a00 100644 --- a/compiler/resolution/implementForallIntents.cpp +++ b/compiler/resolution/implementForallIntents.cpp @@ -274,7 +274,9 @@ static void detupleLeadIdx(Symbol* leadIdxSym, Symbol* leadIdxCopySym, for_vector(Symbol, svar, shadowVars) { lcCall->insertBefore(new DefExpr(svar)); extractFromLeaderYield(lcCall, ++ix, svar, leadIdxSym); +#ifndef HILDE_MM svar->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif } // Finally, remove the original assignment. diff --git a/compiler/resolution/wrappers.cpp b/compiler/resolution/wrappers.cpp index 7a22b4ea05b5..f54766e99bfa 100644 --- a/compiler/resolution/wrappers.cpp +++ b/compiler/resolution/wrappers.cpp @@ -225,9 +225,11 @@ buildDefaultWrapper(FnSymbol* fn, isRefCountedType(wrapper_formal->type)) { // Formal has a type expression attached and is reference counted (?). temp = newTemp("wrap_type_arg"); +#ifndef HILDE_MM if (Symbol* field = fn->_this->type->getField(formal->name, false)) if (field->defPoint->parentSymbol == fn->_this->type->symbol) temp->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif wrapper->insertAtTail(new DefExpr(temp)); // Give the formal its own copy of the type expression. From 5164bc1eaed7c4432532126b92d4035ea38d74fd Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Sat, 13 Dec 2014 15:02:55 -0600 Subject: [PATCH 02/53] Make returnStarTuplesByRefArgs() put the return variable write-back in the right place. I stumbled across this long-standing bug after disabling returnRecordsByRefArguments(). Apparently that routine covered at least some cases that must be handled by returnStartTuplesByRefArgs() when it is disabled. If there is an early return in the routine, it will jump to the exit label. As written, returnStarTuplesByRefArgs() was putting the write-back to the return variable before the exit label. This works fine for the straight-line path, but fails to write out the result when the early return fires. This results in the caller using uninitialized memory, which is generally bad. The fix is a one-liner. The call to insertBeforeReturn() that inserts the write-back was changed to insertBeforeReturnAfterLabel(). That puts the write-back in the right place. --- compiler/passes/returnStarTuplesByRefArgs.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/passes/returnStarTuplesByRefArgs.cpp b/compiler/passes/returnStarTuplesByRefArgs.cpp index 3989bb289eb5..aa9758d6837c 100644 --- a/compiler/passes/returnStarTuplesByRefArgs.cpp +++ b/compiler/passes/returnStarTuplesByRefArgs.cpp @@ -45,7 +45,7 @@ void returnStarTuplesByRefArgs() { ArgSymbol* arg = new ArgSymbol(INTENT_REF, "_ret", ret->type->refType); fn->insertFormalAtTail(arg); fn->retType = dtVoid; - fn->insertBeforeReturn(new CallExpr(PRIM_MOVE, arg, ret)); + fn->insertBeforeReturnAfterLabel(new CallExpr(PRIM_MOVE, arg, ret)); CallExpr* call = toCallExpr(fn->body->body.tail); INT_ASSERT(call && call->isPrimitive(PRIM_RETURN)); call->get(1)->replace(new SymExpr(gVoid)); From 147ac586e2919d16fc0f06ae763d9ee3bbac599f Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Wed, 17 Dec 2014 18:09:31 -0600 Subject: [PATCH 03/53] Disable the removeUnnecessaryAutoCopyCalls pass. This still completes with 50 regressions, so has no impact on the output of the compiler. --- compiler/optimizations/removeUnnecessaryAutoCopyCalls.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/optimizations/removeUnnecessaryAutoCopyCalls.cpp b/compiler/optimizations/removeUnnecessaryAutoCopyCalls.cpp index e2c81e6b1b96..892f6acaad22 100644 --- a/compiler/optimizations/removeUnnecessaryAutoCopyCalls.cpp +++ b/compiler/optimizations/removeUnnecessaryAutoCopyCalls.cpp @@ -439,7 +439,7 @@ static void removePODinitDestroy() void removeUnnecessaryAutoCopyCalls() { - if (fNoRemoveCopyCalls) + if (fNoRemoveCopyCalls || 1) // Disable this pass for a test. return; // From 18485e9ed32c80cba6f17e5cd06c0714e380e000 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Mon, 22 Dec 2014 12:18:42 -0600 Subject: [PATCH 04/53] Add TODOs related to inserted chpl__autoCopy calls. It may be possible to remove some of these calls after the optimized insertion of autoDestroys is working, so this lets me track those places. The TODOs are flagged with TODO AMM for "Automatic Memory Management". diff --git a/compiler/AST/build.cpp b/compiler/AST/build.cpp index 87d86ee..53e9829 100644 --- a/compiler/AST/build.cpp +++ b/compiler/AST/build.cpp @@ -664,6 +664,7 @@ handleArrayTypeCase(FnSymbol* fn, Expr* indices, Expr* iteratorExpr, Expr* expr) thenStmt->insertAtTail(new DefExpr(domain)); // note that we need the below autoCopy until we start reference // counting domains within runtime array types + // TODO: Check if the explicit insertion of an autoCopy is necessary here. thenStmt->insertAtTail(new CallExpr(PRIM_MOVE, domain, new CallExpr("chpl__autoCopy", new CallExpr("chpl__ensureDomainExpr", diff --git a/compiler/passes/normalize.cpp b/compiler/passes/normalize.cpp index 0e9f207..ef8f12c 100644 --- a/compiler/passes/normalize.cpp +++ b/compiler/passes/normalize.cpp @@ -949,11 +949,15 @@ static void init_array_alias(VarSymbol* var, Expr* type, Expr* init, Expr* stmt) if (!type) { partial = new CallExpr("newAlias", gMethodToken, init->remove()); // newAlias is not a method, so we don't set the methodTag + // TODO: newAlias should return a constructed object, which would make + // this autoCopy redundant. stmt->insertAfter(new CallExpr(PRIM_MOVE, var, new CallExpr("chpl__autoCopy", partial))); } else { partial = new CallExpr("reindex", gMethodToken, init->remove()); partial->partialTag = true; partial->methodTag = true; + // TODO: Does reindex return a constructed object? If so, the autoCopy + // call is redundant. stmt->insertAfter(new CallExpr(PRIM_MOVE, var, new CallExpr("chpl__autoCopy", new CallExpr(partial, type->remove())))); } } diff --git a/compiler/resolution/functionResolution.cpp b/compiler/resolution/functionResolution.cpp index 6fb3507..316b150 100644 --- a/compiler/resolution/functionResolution.cpp +++ b/compiler/resolution/functionResolution.cpp @@ -2945,6 +2945,8 @@ static void setFlagsForConstAccess(CallExpr* call, FnSymbol* resolvedFn) // Report an error when storing a sync or single variable into a tuple. // This is because currently we deallocate memory excessively in this case. +// TODO: Review and see if this is still needed after comprehensive automatic +// memory management is in place. static void checkForStoringIntoTuple(CallExpr* call, FnSymbol* resolvedFn) { // Do not perform the checks if: @@ -3751,8 +3753,9 @@ insertFormalTemps(FnSymbol* fn) { // The copy in is needed for "inout", "in" and "const in" intents. // The copy out is needed for "inout" and "out" intents. // Blank intent is treated like "const", and normally copies the formal through -// chpl__autoCopy. +// chpl__autoCopy. (Check that this is still true.) // Note that autoCopy is called in this case, but not for "inout", "in" and "const in". +// TODO: Check that this special behavior is correct and necessary. // Either record-wrapped types are always passed by ref, or some unexpected // behavior will result by applying "in" intents to them. static void addLocalCopiesAndWritebacks(FnSymbol* fn, SymbolMap& formals2vars) @@ -3835,6 +3838,7 @@ static void addLocalCopiesAndWritebacks(FnSymbol* fn, SymbolMap& formals2vars) !ts->hasFlag(FLAG_ITERATOR_CLASS) && !ts->hasFlag(FLAG_ITERATOR_RECORD) && !getSyncFlags(ts).any()) { + // Todo, remove this special case and let parallel handle it. if (fn->hasFlag(FLAG_BEGIN)) { // autoCopy/autoDestroy will be added later, in parallel pass // by insertAutoCopyDestroyForTaskArg() @@ -5313,6 +5317,8 @@ insertValueTemp(Expr* insertPoint, Expr* actual) { // Currently, FLAG_DONOR_FN is only relevant when placed on // chpl__autoCopy(). // +// TODO: Would like to simplify this, so it returns true if the function +// returns a record or tuple and false otherwise. FnSymbol* requiresImplicitDestroy(CallExpr* call) { if (FnSymbol* fn = call->isResolved()) { @@ -7857,8 +7863,9 @@ static void replaceInitPrims(Vec& asts) VarSymbol* tmp = newTemp("_runtime_type_tmp_", runtimeTypeToValueFn->retType); call->getStmtExpr()->insertBefore(new DefExpr(tmp)); call->getStmtExpr()->insertBefore(new CallExpr(PRIM_MOVE, tmp, runtimeTypeToValueCall)); - INT_ASSERT(autoCopyMap.get(tmp->type)); - call->replace(new CallExpr(autoCopyMap.get(tmp->type), tmp)); + FnSymbol* autoCopy = autoCopyMap.get(tmp->type); + INT_ASSERT(autoCopy); + call->replace(new CallExpr(autoCopy, tmp)); } else if (rt->symbol->hasFlag(FLAG_HAS_RUNTIME_TYPE)) { // // This is probably related to a comment that used to handle diff --git a/compiler/resolution/wrappers.cpp b/compiler/resolution/wrappers.cpp index f54766e..2025c27 100644 --- a/compiler/resolution/wrappers.cpp +++ b/compiler/resolution/wrappers.cpp @@ -282,6 +282,9 @@ buildDefaultWrapper(FnSymbol* fn, { Symbol* copyTemp = newTemp("wrap_arg"); wrapper->insertAtTail(new DefExpr(copyTemp)); + // TODO: Insertion of this autocopy can probably be left to + // ownership flow analysis. Since the LHS is a structure member, + // the copy is required, but only if the RHS object is not owned. wrapper->insertAtTail(new CallExpr(PRIM_MOVE, copyTemp, new CallExpr("chpl__autoCopy", temp))); wrapper->insertAtTail( new CallExpr(PRIM_SET_MEMBER, wrapper->_this, --- compiler/AST/build.cpp | 1 + compiler/passes/normalize.cpp | 4 ++++ compiler/resolution/functionResolution.cpp | 13 ++++++++++--- compiler/resolution/wrappers.cpp | 3 +++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/compiler/AST/build.cpp b/compiler/AST/build.cpp index 87d86ee7ea5e..b392dab5bd32 100644 --- a/compiler/AST/build.cpp +++ b/compiler/AST/build.cpp @@ -664,6 +664,7 @@ handleArrayTypeCase(FnSymbol* fn, Expr* indices, Expr* iteratorExpr, Expr* expr) thenStmt->insertAtTail(new DefExpr(domain)); // note that we need the below autoCopy until we start reference // counting domains within runtime array types + // TODO AMM: Check if the explicit insertion of an autoCopy is necessary here. thenStmt->insertAtTail(new CallExpr(PRIM_MOVE, domain, new CallExpr("chpl__autoCopy", new CallExpr("chpl__ensureDomainExpr", diff --git a/compiler/passes/normalize.cpp b/compiler/passes/normalize.cpp index 0e9f2072c0c5..20a0e0ca8949 100644 --- a/compiler/passes/normalize.cpp +++ b/compiler/passes/normalize.cpp @@ -949,11 +949,15 @@ static void init_array_alias(VarSymbol* var, Expr* type, Expr* init, Expr* stmt) if (!type) { partial = new CallExpr("newAlias", gMethodToken, init->remove()); // newAlias is not a method, so we don't set the methodTag + // TODO AMM: newAlias should return a constructed object, which would make + // this autoCopy redundant. stmt->insertAfter(new CallExpr(PRIM_MOVE, var, new CallExpr("chpl__autoCopy", partial))); } else { partial = new CallExpr("reindex", gMethodToken, init->remove()); partial->partialTag = true; partial->methodTag = true; + // TODO AMM: Does reindex return a constructed object? If so, the autoCopy + // call is redundant. stmt->insertAfter(new CallExpr(PRIM_MOVE, var, new CallExpr("chpl__autoCopy", new CallExpr(partial, type->remove())))); } } diff --git a/compiler/resolution/functionResolution.cpp b/compiler/resolution/functionResolution.cpp index 6fb350719dfb..4441c0e5424e 100644 --- a/compiler/resolution/functionResolution.cpp +++ b/compiler/resolution/functionResolution.cpp @@ -2945,6 +2945,8 @@ static void setFlagsForConstAccess(CallExpr* call, FnSymbol* resolvedFn) // Report an error when storing a sync or single variable into a tuple. // This is because currently we deallocate memory excessively in this case. +// TODO AMM: Review and see if this is still needed after comprehensive automatic +// memory management is in place. static void checkForStoringIntoTuple(CallExpr* call, FnSymbol* resolvedFn) { // Do not perform the checks if: @@ -3751,8 +3753,9 @@ insertFormalTemps(FnSymbol* fn) { // The copy in is needed for "inout", "in" and "const in" intents. // The copy out is needed for "inout" and "out" intents. // Blank intent is treated like "const", and normally copies the formal through -// chpl__autoCopy. +// chpl__autoCopy. (Check that this is still true.) // Note that autoCopy is called in this case, but not for "inout", "in" and "const in". +// TODO AMM: Check that this special behavior is correct and necessary. // Either record-wrapped types are always passed by ref, or some unexpected // behavior will result by applying "in" intents to them. static void addLocalCopiesAndWritebacks(FnSymbol* fn, SymbolMap& formals2vars) @@ -3835,6 +3838,7 @@ static void addLocalCopiesAndWritebacks(FnSymbol* fn, SymbolMap& formals2vars) !ts->hasFlag(FLAG_ITERATOR_CLASS) && !ts->hasFlag(FLAG_ITERATOR_RECORD) && !getSyncFlags(ts).any()) { + // TODO AMM: Remove this special case and let parallel handle it. if (fn->hasFlag(FLAG_BEGIN)) { // autoCopy/autoDestroy will be added later, in parallel pass // by insertAutoCopyDestroyForTaskArg() @@ -5313,6 +5317,8 @@ insertValueTemp(Expr* insertPoint, Expr* actual) { // Currently, FLAG_DONOR_FN is only relevant when placed on // chpl__autoCopy(). // +// TODO AMM: Would like to simplify this, so it returns true if the function +// returns a record or tuple and false otherwise. FnSymbol* requiresImplicitDestroy(CallExpr* call) { if (FnSymbol* fn = call->isResolved()) { @@ -7857,8 +7863,9 @@ static void replaceInitPrims(Vec& asts) VarSymbol* tmp = newTemp("_runtime_type_tmp_", runtimeTypeToValueFn->retType); call->getStmtExpr()->insertBefore(new DefExpr(tmp)); call->getStmtExpr()->insertBefore(new CallExpr(PRIM_MOVE, tmp, runtimeTypeToValueCall)); - INT_ASSERT(autoCopyMap.get(tmp->type)); - call->replace(new CallExpr(autoCopyMap.get(tmp->type), tmp)); + FnSymbol* autoCopy = autoCopyMap.get(tmp->type); + INT_ASSERT(autoCopy); + call->replace(new CallExpr(autoCopy, tmp)); } else if (rt->symbol->hasFlag(FLAG_HAS_RUNTIME_TYPE)) { // // This is probably related to a comment that used to handle diff --git a/compiler/resolution/wrappers.cpp b/compiler/resolution/wrappers.cpp index f54766e99bfa..581318049dbe 100644 --- a/compiler/resolution/wrappers.cpp +++ b/compiler/resolution/wrappers.cpp @@ -282,6 +282,9 @@ buildDefaultWrapper(FnSymbol* fn, { Symbol* copyTemp = newTemp("wrap_arg"); wrapper->insertAtTail(new DefExpr(copyTemp)); + // TODO AMM: Insertion of this autocopy can probably be left to + // ownership flow analysis. Since the LHS is a structure member, + // the copy is required, but only if the RHS object is not owned. wrapper->insertAtTail(new CallExpr(PRIM_MOVE, copyTemp, new CallExpr("chpl__autoCopy", temp))); wrapper->insertAtTail( new CallExpr(PRIM_SET_MEMBER, wrapper->_this, From e109fdc9102cde8a0a87473cf4102301e7453cb4 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Tue, 23 Dec 2014 23:33:46 -0600 Subject: [PATCH 05/53] Add binary operators to BitVec implementation. Also, use size_t in place of int, for closer compatibility with std::bitset and the boost dynamic_bitset. Added a copy-constructor and the copy operation to support + (set union) and - (set difference). Updated bb.cpp, copyPropagation.cpp and loopInvariantCodeMotion.cpp, clients of BitVec, because now the size it returns and the ndata field are unsigned (size_t). --- compiler/AST/bb.cpp | 8 +- compiler/adt/bitVec.cpp | 96 ++++++++++++------- compiler/include/bitVec.h | 59 +++++++++--- compiler/optimizations/copyPropagation.cpp | 2 +- .../optimizations/loopInvariantCodeMotion.cpp | 2 +- 5 files changed, 115 insertions(+), 52 deletions(-) diff --git a/compiler/AST/bb.cpp b/compiler/AST/bb.cpp index 5f6abc667615..3e1053707537 100644 --- a/compiler/AST/bb.cpp +++ b/compiler/AST/bb.cpp @@ -377,7 +377,7 @@ void BasicBlock::backwardFlowAnalysis(FnSymbol* fn, iterate = false; for_vector(BasicBlock, bb, *fn->basicBlocks) { - for (int j = 0; j < IN[i]->ndata; j++) { + for (size_t j = 0; j < IN[i]->ndata; j++) { unsigned int new_in = (OUT[i]->data[j] & ~KILL[i]->data[j]) | GEN[i]->data[j]; unsigned int new_out = 0; @@ -440,7 +440,7 @@ void BasicBlock::forwardFlowAnalysis(FnSymbol* fn, BasicBlock* bb = (*fn->basicBlocks)[i]; bool change = false; - for (int j = 0; j < IN[i]->ndata; j++) { + for (size_t j = 0; j < IN[i]->ndata; j++) { if (bb->ins.size() > 0) { unsigned int new_in = (intersect) ? (unsigned int) (-1) : 0; @@ -535,7 +535,7 @@ void BasicBlock::printLocalsVectorSets(std::vector& sets, Vec for_vector(BitVec, set, sets) { printf("%2d: ", i); - for (int j = 0; j < set->size(); j++) { + for (size_t j = 0; j < set->size(); j++) { if (set->get(j)) printf("%s[%d] ", locals.v[j]->name, locals.v[j]->id); } @@ -554,7 +554,7 @@ void BasicBlock::printBitVectorSets(std::vector& sets) { for_vector(BitVec, set, sets) { printf("%2d: ", i); - for (int j = 0; j < set->size(); j++) { + for (size_t j = 0; j < set->size(); j++) { printf("%d", (set->get(j)) ? 1 : 0); if ((j+1) % 10 == 0) printf(" "); } diff --git a/compiler/adt/bitVec.cpp b/compiler/adt/bitVec.cpp index 4d46e330143f..82e5a19e4de4 100644 --- a/compiler/adt/bitVec.cpp +++ b/compiler/adt/bitVec.cpp @@ -22,7 +22,7 @@ #define TYPE unsigned -BitVec::BitVec(int in_size) { +BitVec::BitVec(size_t in_size) { if (in_size == 0) { ndata = 0; this->in_size = 0; @@ -35,39 +35,50 @@ BitVec::BitVec(int in_size) { } +BitVec::BitVec(const BitVec& rhs) +: data(NULL), in_size(rhs.in_size), ndata(rhs.ndata) +{ + if (ndata > 0) + { + data = (TYPE*)calloc(ndata, sizeof(TYPE)); + copy(rhs); + } +} + + BitVec::~BitVec() { free(data); } void BitVec::clear() { - for (int i = 0; i < ndata; i++) + for (size_t i = 0; i < ndata; i++) data[i] = 0; } -bool BitVec::get(int i) { - int j = i / (sizeof(TYPE)<<3); - int k = i - j*(sizeof(TYPE)<<3); +bool BitVec::get(size_t i) { + size_t j = i / (sizeof(TYPE)<<3); + size_t k = i - j*(sizeof(TYPE)<<3); return data[j] & (1 << k); } -void BitVec::unset(int i) { - int j = i / (sizeof(TYPE)<<3); - int k = i - j*(sizeof(TYPE)<<3); +void BitVec::unset(size_t i) { + size_t j = i / (sizeof(TYPE)<<3); + size_t k = i - j*(sizeof(TYPE)<<3); data[j] &= ((TYPE)-1) - (1 << k); } void BitVec::disjunction(BitVec& other) { - for (int i = 0; i < ndata; i++) + for (size_t i = 0; i < ndata; i++) data[i] |= other.data[i]; } void BitVec::intersection(BitVec& other) { - for (int i = 0; i < ndata; i++) + for (size_t i = 0; i < ndata; i++) data[i] &= other.data[i]; } @@ -83,7 +94,7 @@ void BitVec::intersection(BitVec& other) { bool BitVec::equals(BitVec& other) { - for(int i = 0; i < ndata; i++) { + for(size_t i = 0; i < ndata; i++) { if(data[i] != other.data[i]) { return false; } @@ -93,40 +104,55 @@ bool BitVec::equals(BitVec& other) { void BitVec::set() { - for (int i = 0; i < ndata; i++) + for (size_t i = 0; i < ndata; i++) data[i] = ~0; } -void BitVec::set(int i) { - int j = i / (sizeof(TYPE)<<3); - int k = i - j*(sizeof(TYPE)<<3); +void BitVec::set(size_t i) { + size_t j = i / (sizeof(TYPE)<<3); + size_t k = i - j*(sizeof(TYPE)<<3); data[j] |= 1 << k; } void BitVec::reset() { - for (int i = 0; i < ndata; i++) + for (size_t i = 0; i < ndata; i++) data[i] = 0; } -void BitVec::reset(int i) { - int j = i / (sizeof(TYPE)<<3); - int k = i - j*(sizeof(TYPE)<<3); - data[j] &= ((int)-1) - (1 << k); +void BitVec::reset(size_t i) { + size_t j = i / (sizeof(TYPE)<<3); + size_t k = i - j*(sizeof(TYPE)<<3); + data[j] &= ((size_t)-1) - (1 << k); +} + + +void BitVec::copy(const BitVec& other) { + for (size_t i = 0; i < ndata; ++i) + data[i] = other.data[i]; +} + + +void BitVec::copy(size_t i, bool value) { + size_t j = i / (sizeof(TYPE)<<3); + size_t k = i - j*(sizeof(TYPE)<<3); + data[j] &= ~(1 << k); + if (value) + data[j] |= (1 << k); } void BitVec::flip() { - for (int i = 0; i < ndata; i++) + for (size_t i = 0; i < ndata; i++) data[i] = ~data[i]; } -void BitVec::flip(int i) { - int j = i / (sizeof(TYPE)<<3); - int k = i - j*(sizeof(TYPE)<<3); +void BitVec::flip(size_t i) { + size_t j = i / (sizeof(TYPE)<<3); + size_t k = i - j*(sizeof(TYPE)<<3); data[j] ^= 1 << k; } @@ -138,11 +164,11 @@ void BitVec::flip(int i) { * hex manipulation (I have no idea what the name of that bit counting * algorithm is called) */ -int BitVec::count() { - int count = 0; - for (int i = 0; i < ndata; i++) { - int localCount ; - int x = data[i]; +size_t BitVec::count() { + size_t count = 0; + for (size_t i = 0; i < ndata; i++) { + size_t localCount ; + size_t x = data[i]; for (localCount=0; x; localCount++) { x &= x-1; } @@ -152,20 +178,20 @@ int BitVec::count() { } -int BitVec::size() { - return (int)in_size; +size_t BitVec::size() { + return in_size; } -bool BitVec::test(int i) { - int j = i / (sizeof(TYPE)<<3); - int k = i - j*(sizeof(TYPE)<<3); +bool BitVec::test(size_t i) { + size_t j = i / (sizeof(TYPE)<<3); + size_t k = i - j*(sizeof(TYPE)<<3); return data[j] & (1 << k); } bool BitVec::any() { - for (int i = 0; i < ndata; i++) { + for (size_t i = 0; i < ndata; i++) { if(data[i] > 0) { return true; } diff --git a/compiler/include/bitVec.h b/compiler/include/bitVec.h index 6e2802606513..b77ea4fd0dc5 100644 --- a/compiler/include/bitVec.h +++ b/compiler/include/bitVec.h @@ -23,14 +23,16 @@ class BitVec { public: unsigned* data; - int in_size; - int ndata; + size_t in_size; + size_t ndata; - BitVec(int in_size); + BitVec(size_t in_size); + BitVec(const BitVec& rhs); ~BitVec(); void clear(); - bool get(int i); - void unset(int i); + bool get(size_t i); + bool operator[](size_t i) { return get(i); } + void unset(size_t i); void disjunction(BitVec& other); void intersection(BitVec& other); @@ -39,16 +41,51 @@ class BitVec { // boosts dynamic bitset if that gets into the STL, or we start using boost bool equals(BitVec& other); void set(); - void set(int i); + void set(size_t i); void reset(); - void reset(int i); + void reset(size_t i); + void copy(const BitVec& other); + void copy(size_t i, bool value); void flip(); - void flip(int i); - int count(); - int size(); - bool test(int i); + void flip(size_t i); + size_t count(); + size_t size(); + bool test(size_t i); bool any(); bool none(); }; +inline BitVec* operator+(BitVec& a, BitVec& b) +{ + BitVec* result = new BitVec(a); + result->disjunction(b); + return result; +} + +// Usurps its left argument according to the optional adoption pattern. +inline BitVec* operator+(BitVec* a, BitVec& b) +{ + BitVec* result = a; + result->disjunction(b); + return result; +} + +inline BitVec* operator-(BitVec& a, BitVec& b) +{ + BitVec* result = new BitVec(b); + result->flip(); + result->intersection(a); + return result; +} + +inline BitVec* operator-(BitVec* a, BitVec& b) +{ + BitVec* result = a; + BitVec* temp = new BitVec(b); + temp->flip(); + result->intersection(*temp); + delete temp; temp = 0; + return result; +} + #endif diff --git a/compiler/optimizations/copyPropagation.cpp b/compiler/optimizations/copyPropagation.cpp index 61f35d83dfd9..68e857bf26f8 100644 --- a/compiler/optimizations/copyPropagation.cpp +++ b/compiler/optimizations/copyPropagation.cpp @@ -954,7 +954,7 @@ static void computeKillSets(FnSymbol* fn, // Use killSet to initialize the KILL set for this block. // It's OK if we include the pairs from this block in KILL[i] because we // put them back when we add in the COPY set. - for (int j = 0; j < KILL[i]->size(); ++j) + for (size_t j = 0; j < KILL[i]->size(); ++j) if (killSet.find(availablePairs[j].first) != killSet.end() || killSet.find(availablePairs[j].second) != killSet.end()) KILL[i]->set(j); diff --git a/compiler/optimizations/loopInvariantCodeMotion.cpp b/compiler/optimizations/loopInvariantCodeMotion.cpp index c1f5e65e63f7..c7bc830504e2 100644 --- a/compiler/optimizations/loopInvariantCodeMotion.cpp +++ b/compiler/optimizations/loopInvariantCodeMotion.cpp @@ -1010,7 +1010,7 @@ static bool defDominatesAllExits(Loop* loop, SymExpr* def, std::vector& BitVec* bitExits = loop->getBitExits(); - for(int i = 0; i < bitExits->size(); i++) { + for(size_t i = 0; i < bitExits->size(); i++) { if(bitExits->test(i)) { if(dominates(defBlock, i, dominators) == false) { return false; From 0ce0527f7d742b2ecdef652edf6916fd722a5cf4 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Tue, 23 Dec 2014 23:36:26 -0600 Subject: [PATCH 06/53] Initial implementation of flow-based destructor call insertion. It compiles. --- compiler/include/passes.h | 1 + compiler/make/Makefile.compiler.head | 2 +- compiler/passes/Makefile.share | 1 + compiler/passes/insertAutoCopyAutoDestroy.cpp | 524 ++++++++++++++++++ compiler/resolution/callDestructors.cpp | 1 + 5 files changed, 528 insertions(+), 1 deletion(-) create mode 100644 compiler/passes/insertAutoCopyAutoDestroy.cpp diff --git a/compiler/include/passes.h b/compiler/include/passes.h index 94d000cb5e49..f3f3a26c962f 100644 --- a/compiler/include/passes.h +++ b/compiler/include/passes.h @@ -50,6 +50,7 @@ void expandExternArrayCalls(); void flattenClasses(); void flattenFunctions(); void inlineFunctions(); +void insertAutoCopyAutoDestroy(); void insertLineNumbers(); void insertWideReferences(); void narrowWideReferences(); diff --git a/compiler/make/Makefile.compiler.head b/compiler/make/Makefile.compiler.head index 811c3d2d6147..1b95e503168a 100644 --- a/compiler/make/Makefile.compiler.head +++ b/compiler/make/Makefile.compiler.head @@ -34,7 +34,7 @@ endif include $(CHPL_MAKE_HOME)/make/Makefile.base INCL_CFLAGS = -I. -I$(COMPILER_ROOT)/include/$(CHPL_MAKE_HOST_PLATFORM) -I$(COMPILER_ROOT)/include $(LLVM_INCLUDES) -COMP_CFLAGS += $(INCL_CFLAGS) +COMP_CFLAGS += $(INCL_CFLAGS) -DHILDE_MM=1 # # add gc-related stuff diff --git a/compiler/passes/Makefile.share b/compiler/passes/Makefile.share index 4531f12d1ed8..1a209f1c086e 100644 --- a/compiler/passes/Makefile.share +++ b/compiler/passes/Makefile.share @@ -33,6 +33,7 @@ PASSES_SRCS = \ filesToAST.cpp \ flattenClasses.cpp \ flattenFunctions.cpp \ + insertAutoCopyAutoDestroy.cpp \ insertLineNumbers.cpp \ insertWideReferences.cpp \ normalize.cpp \ diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp new file mode 100644 index 000000000000..3dd057a14b7e --- /dev/null +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -0,0 +1,524 @@ +/* + * Copyright 2004-2014 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// insertAutoCopyAutoDestroy +// +// Inserts copy-constructor and destructor calls as needed, by tracking the +// "ownership" of record objects. +// +//############################################################################# +//# +//# Ownership represents the "liveness" of a record object in the sense that it +//# may contain heap-allocated data. After a record object is constructed, any +//# of its fields of class type may refer to a class object. In that state, any +//# heap-allocated fields in the record are "owned" by the record. Calling the +//# destructor for that record type gives the record author the opportunity to +//# clean up any contained class fields and thereby avoid leaking memory. After +//# the destructor is called, the record variable no longer owns any +//# heap-allocated data, so that data is "unowned". +//# +//# In the simplest view, ownership is a binary state: construction changes the +//# state from false to true; destruction changes the state from true to false. +//# +//# Ownership of a record can also be transferred by a bit-wise copy. When a +//# bit-wise copy of a record is made, class fields (which are implemented as +//# pointers) are copied verbatim. That means that both record objects point to +//# the same class data. Ownership is then shared between them. Some time +//# before both copies go out of scope, one of them needs to call its +//# destructor, the other should not (to avoid double-deletion errors). +//# +//# When ownership is shared in this way, we can perform a sort of internal +//# reference-counting, maintaining a list of aliases, and ensuring that the +//# destructor is called on one of them before the last member of that alias set +//# goes out of scope. +//# +//# For these purposes, we assume that none of the class fields of an alias is +//# updated, so they remain equivalent throughout. If that is not true, then +//# the two variables represent independent variables. In that case, a +//# copy-constructor call should have been inserted because each variable should +//# have its own initialization. So its absence would indicate an error +//# somewhere upstream. +//# +//# We assume that the input AST contains the minimum number of +//# copy-constructor calls required to provide the specified semantics. That +//# is, record values passed by in intent must be copied. We do not yet +//# attempt to pass ownership of an object into a called routine. Two kinds of +//# variable whose ownersihp must be true before exiting a routine are: +//# - The return value variable +//# - A record field in a record. +//# If ownership of any of these is false when they (or their container) is +//# about to go out of scope, a copy must be made to bring ownership to the +//# expected state. Otherwise, ownership can be shared and a copy potentially saved. +//# +//# After copy-constructor calls have been added, the routine has its full +//# complement of construcotr calls. Correct AMM is then just a matter of +//# inserting the minimum number of destructor calls to drive the ownership of +//# all local variables to false before the routine ends. Dataflow analysis is +//# used to propagate this constraint backwards, in case ownership is +//# transferred out of a variable along one path but not another. +//# +//############################################################################# + +// The sets used for flow analysis are: +// GEN -- The set of symbols whose ownership transitions to true by the end of +// the block. +// KILL -- The set of symbols whose ownership transitions to false by the end +// of the block. +// IN -- The set of symbols that are owned at the beginning of the block. +// OUT -- The set of symbols that are owned at the end of the block. +// +// GEN[i] contains a true bit corresponding to each symbol that is constructed +// in that block. We expect that symbol is constructed only once, so if IN[i] +// and GEN[i] have a true bit in the same position, we can flag an internal +// error. This condition is unlikely, so at best it should be added as a +// verify check. +// +// KILL[i] contains a true bit corresponding to each symbol that is destroyed +// or whose ownership is usurped in that block. We keep a separate list of +// aliases. When a bitwise copy is made, we treat the new copy as if it has +// been constructed without changing the state of the original. When any +// member of that set of aliases is destroyed or usurped, all members of the +// set are added to KILL[i]. +// +// We start by performing forward flow analysis to establish the value of IN. +// IN[i] is the intersection of OUT[pred(i)] for all predecessors of block i. +// It is desirable to allow ownership to flow through to the end of the +// function (to a final block with many predecessors). That way, we can +// consume liveness with a minimum of destructor calls. However, if we cannot +// depend on a given symbol being owned at the beginning of a block, then its +// destructor must be pushed back up the flow graph. For example, we might +// pass ownership to a field or global variable on one path; then we need a +// destructor call to get rid of ownership on the other path(s). +// +// For a block that contains a constructor followed by a destructor, both +// GEN[i] and KILL[i] will be zero. If they are both true, that represents a +// symbol being destroyed and then reused, so GEN takes precedence over KILL +// in forward flow: OUT[i] = IN[i] - KILL[i] + GEN[i]. +// +// For backward flow, we compute OUT[i] as the intersection of IN[succ(i)] for +// all successors of block i. (No information is propagated backward through +// the blocks, so this can be done in one iteration.) Then, in the local +// traversal the follows, for any symbol that remains owned at the end of block +// i where its OUT[i] is false, we add a destructor call to make that condition +// true. +// + +#include "passes.h" // The main routine is declared here. + +#include "bb.h" +#include "expr.h" +#include "symbol.h" +#include "bitVec.h" +#include "stlUtil.h" +#include "astutil.h" +#include "resolution.h" // For AutoDestroyMap. + +#include +#include + +typedef std::vector FlowSet; +typedef std::vector SymbolVector; +typedef std::map SymbolIndexMap; +typedef SymbolIndexMap::value_type SymbolIndexElement; +typedef std::map AliasVectorMap; +typedef AliasVectorMap::value_type AliasMapElement; +typedef std::vector SymExprVector; +typedef std::vector DefExprVector; + + +static void createFlowSet(std::vector& set, + size_t nbbs, + size_t size) +{ + // Create a BitVec of length size for each block. + for (size_t i = 0; i < nbbs; ++i) + set.push_back(new BitVec(size)); +} + + +static void destroyFlowSet(std::vector set) +{ + for_vector(BitVec, vec, set) + delete vec, vec = 0; +} + + +// An alias vector map is map from symbols to lists of symbols, but all of the +// symbols in the same clique share the same list. So, to destroy all the +// lists in the map, we find the list through the first member of the clique, +// then set the map element for each member in the list to NULL. (The list +// contains the element though which the list is accessed, so this detaches the +// list from all map elements.) Then we delete the list. (The symbols +// contained in the list are owned elsewhere.) +static void destroyAliasVectorMap(AliasVectorMap& aliases) +{ + for (AliasVectorMap::iterator it = aliases.begin(); + it != aliases.end(); ++it) + { + if (it->second == NULL) + continue; + + SymbolVector* aliasList = it->second; + for_vector(Symbol, sym, *aliasList) + aliases[sym] = NULL; + + delete aliasList; + } +} + + +// Scans the body of the given function and inserts all of the variable and +// argument symbols it finds into the vector of symbols. Bits in the flow +// analysis bit-vectors correspond to the entries in this vector. +// Also constructs an index map, to make it easier to find the index of a +// symbol in the vector. (Otherwise, a linear search is required.) +static void +extractSymbols(FnSymbol* fn, + SymbolVector& symbols, SymbolIndexMap& symbolIndex) +{ + DefExprVector defExprs; + collectDefExprsSTL(fn, defExprs); + + for_vector(DefExpr, def, defExprs) + { + Symbol* sym = def->sym; + symbolIndex.insert(SymbolIndexElement(sym, symbols.size())); + symbols.push_back(sym); + + // We expect the symbolIndex to return the index of that symbol in the + // symbols vector. + INT_ASSERT(symbols[symbolIndex[sym]] == sym); + } +} + + +static void processConstructor(CallExpr* call, SymExpr* se, + BitVec* gen, AliasVectorMap& aliases, + const SymbolIndexMap& symbolIndex) +{ + // In the current incarnation, we expect construction to look like: + // ('move' lhs (construct )) + // When constructors turn into methods, this will look a bit different. + if (call->isPrimitive(PRIM_MOVE) || + call->isPrimitive(PRIM_ASSIGN)) + { + if (CallExpr* rhsCall = toCallExpr(call->get(2))) + { + if (FnSymbol* fn = rhsCall->isResolved()) + { + if (fn->hasFlag(FLAG_CONSTRUCTOR) || + fn->hasFlag(FLAG_INIT_COPY_FN) || + fn->hasFlag(FLAG_AUTO_COPY_FN)) + { + Symbol* sym = se->var; + size_t index = symbolIndex.at(sym); + // We expect that each symbol gets constructed only once, so if we are + // about to set a bit in the gen set, it cannot already be true. + INT_ASSERT(gen->get(index) == false); + // If this assumption turns out to be false, it means we are reusing + // symbols. That case can be accommodated, but it means we have to + // insert a destructor call ahead of the symbol's reinitialization. + gen->set(index); + // If we start the alias list with the first entry, then we can process + // it unequivocally. + SymbolVector* aliasList = new SymbolVector(); + aliasList->push_back(sym); + aliases.insert(AliasMapElement(sym, aliasList)); + } + } + } + } +} + + +// Add the alias to the end of the alias list accessible through the original +// symbol, and then share the alias list withs the alias. +static void addAlias(Symbol* orig, Symbol* alias, + AliasVectorMap& aliases) +{ + SymbolVector* aliasList = aliases[orig]; + // Implicit assertion that aliasList is non-NULL. + aliasList->push_back(alias); + aliases.insert(AliasMapElement(alias, aliasList)); +} + + +static void processMove(CallExpr* call, SymExpr* se, BitVec* gen, AliasVectorMap& aliases, + const SymbolIndexMap& symbolIndex) +{ + // We only care about bitwise copies here. + if (call->isPrimitive(PRIM_MOVE) || + call->isPrimitive(PRIM_ASSIGN)) + { + // We'll key off the LHS. + // Meaning that we will only pay attention to the expression if the SymExpr + // is in the LHS position in the primitive. + if (SymExpr* lhs = toSymExpr(call->get(1))) + { + if (lhs == se) + { + // We only care about bitwise copies from one symbol to another. + if (SymExpr* rhs = toSymExpr(call->get(2))) + { + Symbol* lsym = lhs->var; + Symbol* rsym = rhs->var; + size_t lindex = symbolIndex.at(lsym); + size_t rindex = symbolIndex.at(rsym); + // Copy ownership state from RHS. + INT_ASSERT(gen->get(lindex) == false); + gen->copy(lindex, gen->get(rindex)); + addAlias(lsym, rsym, aliases); + } + } + } + } +} + + +static void processDestructor(SymExpr* se, + BitVec* kill, AliasVectorMap& aliases, + const SymbolIndexMap& symbolIndex) +{ + // Add all members of an alias clique to the kill set. + // All members of an alias clique point to the same SymbolVector, so we only + // need to look up one arbitrarily and then run the list. + Symbol* sym = se->var; + SymbolVector& aliasList = *aliases[sym]; + for_vector(Symbol, alias, aliasList) + { + size_t index = symbolIndex.at(alias); + // We expect a symbol to be live when it is killed. + INT_ASSERT(kill->get(index) == false); + kill->set(index); + } + + // We don't bother updating the alias list under the assumption that + // symbols are not reused. If that turns out to be false, we have to + // remove the alias clique from aliases here. +} + + +// If this call acts like a destructor, then add the symbols it affects to the +// kill set. +static void processDestructor(CallExpr* call, SymExpr* se, BitVec* kill, + AliasVectorMap& aliases, + const SymbolIndexMap& symbolIndex) +{ + if (FnSymbol* fn = call->isResolved()) + { + // This is a function call. + + // The only one we're interested in right now is a destructor call. + if (fn->hasFlag(FLAG_DESTRUCTOR)) + { + // Paranoid check: This SymExpr is the thing being destroyed, right? + INT_ASSERT(call->get(1) == se); + + processDestructor(se, kill, aliases, symbolIndex); + } + } + else + { + // This is a primitive. + if (call->isPrimitive(PRIM_RETURN)) + { + // Returns act like destructors. + processDestructor(se, kill, aliases, symbolIndex); + } + } +} + + +static void computeTransitions(SymExprVector& symExprs, + BitVec* gen, BitVec* kill, + AliasVectorMap& aliases, + const SymbolIndexMap& symbolIndex) +{ + for_vector(SymExpr, se, symExprs) + { + // We are only interested in call expressions involving the SymExpr. + if (CallExpr* call = toCallExpr(se->parentExpr)) + { + processConstructor(call, se, gen, aliases, symbolIndex); + processMove(call, se, gen, aliases, symbolIndex); + processDestructor(call, se, kill, aliases, symbolIndex); + } + } +} + + +// Look for expressions that create, destroy or transfer ownership of a record +// object. +// Right now, we can get away with just looking for MOVE or ASSIGN primitives +// to determine where ownership is created or transferred. When constructors +// become methods, we'll key off the CONSTRUCTOR flag and modify the state of +// the first (receiver) argument. +static void computeTransitions(BasicBlock& bb, BitVec* gen, BitVec* kill, + AliasVectorMap& aliases, + const SymbolIndexMap& symbolIndex) +{ + for_vector(Expr, expr, bb.exprs) + { + // Are all expressions in this list mutually exclusive? + // Can we speed things up by processing only statement expressions? + + SymExprVector symExprs; + collectSymExprsSTL(expr, symExprs); + + computeTransitions(symExprs, gen, kill, aliases, symbolIndex); + } +} + + +static void computeTransitions(FnSymbol* fn, + FlowSet& GEN, FlowSet& KILL, + AliasVectorMap& aliases, + const SymbolIndexMap& symbolIndex) +{ + size_t nbbs = fn->basicBlocks->size(); + for (size_t i = 0; i < nbbs; ++i) + { + computeTransitions(*(*fn->basicBlocks)[i], GEN[i], KILL[i], aliases, symbolIndex); + } +} + + +// This predicate determines if the given statement is a jump. +static bool isJump(Expr* stmt) +{ + // A goto is definitely a jump. + if (isGotoStmt(stmt)) + return true; + + // A return primitive works like a jump. (Nothing should appear after it.) + if (CallExpr* call = toCallExpr(stmt)) + if (call->isPrimitive(PRIM_RETURN)) + return true; + + return false; +} + + +// At the end of this basic block, insert an autodestroy for each symbol +// specified by the given bit-vector. +static void insertAutoDestroy(BasicBlock& bb, BitVec* to_kill, + const SymbolVector& symbols) +{ + // Find the last statement in the block. + Expr*& last = bb.exprs.back(); + Expr* stmt = last->getStmtExpr(); + bool isjump = isJump(stmt); + + // For each true bit in the bit vector, add an autodestroy call. + for (size_t j = 0; j < to_kill->size(); ++j) + { + if (to_kill->get(j)) + { + Symbol* sym = symbols[j]; + CallExpr* autoDestroyCall = new CallExpr(autoDestroyMap.get(sym->type), sym); + if (isjump) + stmt->insertBefore(autoDestroyCall); + else + stmt->insertAfter(autoDestroyCall); + } + } +} + +static void insertAutoDestroy(FnSymbol* fn, + FlowSet& GEN, FlowSet& KILL, + FlowSet& IN, FlowSet& OUT, + const SymbolVector& symbols) +{ + size_t nbbs = fn->basicBlocks->size(); + for (size_t i = 0; i < nbbs; i++) + { + // We need to insert an autodestroy call for each symbol that is owned + // (live) at the end of the block but is unowned (dead) in the OUT set. + BasicBlock& bb = *(*fn->basicBlocks)[i]; + BitVec* to_kill = *IN[i] + *GEN[i] - *KILL[i] - *OUT[i]; + insertAutoDestroy(bb, to_kill, symbols); + delete to_kill; to_kill = 0; + } +} + +static void insertAutoDestroy(FnSymbol* fn) +{ + BasicBlock::buildBasicBlocks(fn); + size_t nbbs = fn->basicBlocks->size(); + + SymbolVector symbols; + SymbolIndexMap symbolIndex; + extractSymbols(fn, symbols, symbolIndex); + + size_t size = symbols.size(); + +#if DEBUG_AMM + if (debug > 0) + { + printf("\n"); + list_view(fn); + + BasicBlock::printBasicBlocks(fn); + } +#endif + + FlowSet GEN; + FlowSet KILL; + FlowSet IN; + FlowSet OUT; + + createFlowSet(GEN, nbbs, size); + createFlowSet(KILL, nbbs, size); + createFlowSet(IN, nbbs, size); + createFlowSet(OUT, nbbs, size); + + AliasVectorMap aliases; + + computeTransitions(fn, GEN, KILL, aliases, symbolIndex); + + BasicBlock::forwardFlowAnalysis(fn, GEN, KILL, IN, OUT, true); + + insertAutoDestroy(fn, GEN, KILL, IN, OUT, symbols); + + destroyAliasVectorMap(aliases); + + destroyFlowSet(GEN); + destroyFlowSet(KILL); + destroyFlowSet(IN); + destroyFlowSet(OUT); +} + + +static void insertAutoDestroy() +{ + forv_Vec(FnSymbol, fn, gFnSymbols) + { + insertAutoDestroy(fn); + } +} + + +void insertAutoCopyAutoDestroy() +{ + // First of all, just insert autoDestroy calls. + // We assume that all necessary autoCopy calls have been added. + // As an enhancement/optimization, we'll add a separate pass to compute and + // add the minimum number of autoCopy calls required. + insertAutoDestroy(); +} diff --git a/compiler/resolution/callDestructors.cpp b/compiler/resolution/callDestructors.cpp index 5b797f75334c..f3945fa7d0b3 100644 --- a/compiler/resolution/callDestructors.cpp +++ b/compiler/resolution/callDestructors.cpp @@ -924,4 +924,5 @@ callDestructors() { insertGlobalAutoDestroyCalls(); #endif insertReferenceTemps(); +// insertAutoCopyAutoDestroy(); } From 4b181112c1e7dd5f5cdbf1f76d5080938678d686 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Sat, 27 Dec 2014 11:07:27 -0600 Subject: [PATCH 07/53] Runs hello.chpl. Added filters in extractSymbols() to ignore symbols that are not VarSymbols or ArgSymbols, extern symbols and symbols not of a record type. Filtering out symbols of class type also filters out references. Maybe this is too clever, but at least fits well with the style of programming currently in place. If the representation of reference types changes in the future, this is one place where the code will have to be amended. At least that case is covered by a helpful comment. Moved the initialization of the alias table up into extractSymbols() because the expectation that each symbol is initialized before being copied (or copied into) through a move is currently false. Split off the isConstructor() test as a predicate. Added a test in processMove(), so we can flag when an uninitialized symbol is copied. This would probably result in a large number of false positives before flow analysis is carried out, because each block starts with an empty gen set. We now filter by the members of the symbolIndex, so we ignore symbols that are not local (including module-level and extern symbols) and not variables or arguments. Had to add a test to skip degenerate basic blocks. Why do we still have degenerate basic blocks? We skip functions that are prototypes (including extern functions) altogether. Re-enabled insertGlobalAutoDestroyCalls() and activated the new insertAutoDestroyCalls() mini-pass. --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 166 ++++++++++++++---- compiler/resolution/callDestructors.cpp | 9 +- 2 files changed, 131 insertions(+), 44 deletions(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index 3dd057a14b7e..ac234f8fe4cd 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -17,6 +17,9 @@ * limitations under the License. */ +// TODO: This should be moved to symbol.cpp +bool fWarnOwnership = false; + // insertAutoCopyAutoDestroy // // Inserts copy-constructor and destructor calls as needed, by tracking the @@ -184,13 +187,18 @@ static void destroyAliasVectorMap(AliasVectorMap& aliases) // Scans the body of the given function and inserts all of the variable and -// argument symbols it finds into the vector of symbols. Bits in the flow +// argument symbols defined in it into the vector of symbols. Bits in the flow // analysis bit-vectors correspond to the entries in this vector. // Also constructs an index map, to make it easier to find the index of a // symbol in the vector. (Otherwise, a linear search is required.) +// The alias map can also be populated at the same time. +// TODO: Split the alias list off as a separate class, mostly to make it easy +// to document the data structure it implements. static void extractSymbols(FnSymbol* fn, - SymbolVector& symbols, SymbolIndexMap& symbolIndex) + SymbolVector& symbols, + SymbolIndexMap& symbolIndex, + AliasVectorMap& aliases) { DefExprVector defExprs; collectDefExprsSTL(fn, defExprs); @@ -198,18 +206,80 @@ extractSymbols(FnSymbol* fn, for_vector(DefExpr, def, defExprs) { Symbol* sym = def->sym; + + // We are interested only in arguments and variables. + if (! (toArgSymbol(sym) || toVarSymbol(sym))) + continue; + + Type* type = sym->type; + + // TODO: Extern record types also do not have constructors and + // destructors. To treat them uniformly, we would have to enforce that + // extern types supply constructors and destructors and/or supply them + // internally. + TypeSymbol* ts = type->symbol; + if (ts->hasFlag(FLAG_EXTERN)) + continue; + + // We are concerned only with record types. + // TODO: This is too bad, because it would be nice to be able to treat all + // value types uniformly. But for that to work, arguments of fundamental + // type must be constructed by having their values piped through a copy + // constructor. Currently, that is not the case. + + // We are interested only in records passed by value. Records passed by + // ref appear be a class in the current AST because _ref(T) is a class type. + AggregateType* at = toAggregateType(type); + if (at == NULL) + // Not an aggregate type, so not a record. + continue; + if (!at->isRecord()) + // Not a record. + continue; + symbolIndex.insert(SymbolIndexElement(sym, symbols.size())); symbols.push_back(sym); // We expect the symbolIndex to return the index of that symbol in the // symbols vector. INT_ASSERT(symbols[symbolIndex[sym]] == sym); + + // Initialize each entry in the alias map with a list of symbols + // containingh the symbol itself. + SymbolVector* aliasList = new SymbolVector(); + aliasList->push_back(sym); + aliases.insert(AliasMapElement(sym, aliasList)); + } +} + + +// Returns true if this function returns a fully-constructed value; false otherwise. +static bool isConstructor(CallExpr* call) +{ + if (FnSymbol* fn = call->isResolved()) + { + // A "normal" function. + + // Return values of class type are ruled out. + Type* retType = fn->retType; + if (AggregateType* at = toAggregateType(retType)) + if (at->isClass()) + return false; } + else + { + // A primitive. + if (AggregateType* at = toAggregateType(call->typeInfo())) + if (at->isClass()) + return false; + } + + return true; } static void processConstructor(CallExpr* call, SymExpr* se, - BitVec* gen, AliasVectorMap& aliases, + BitVec* gen, const SymbolIndexMap& symbolIndex) { // In the current incarnation, we expect construction to look like: @@ -220,42 +290,37 @@ static void processConstructor(CallExpr* call, SymExpr* se, { if (CallExpr* rhsCall = toCallExpr(call->get(2))) { - if (FnSymbol* fn = rhsCall->isResolved()) + if (isConstructor(rhsCall)) { - if (fn->hasFlag(FLAG_CONSTRUCTOR) || - fn->hasFlag(FLAG_INIT_COPY_FN) || - fn->hasFlag(FLAG_AUTO_COPY_FN)) - { - Symbol* sym = se->var; - size_t index = symbolIndex.at(sym); - // We expect that each symbol gets constructed only once, so if we are - // about to set a bit in the gen set, it cannot already be true. - INT_ASSERT(gen->get(index) == false); - // If this assumption turns out to be false, it means we are reusing - // symbols. That case can be accommodated, but it means we have to - // insert a destructor call ahead of the symbol's reinitialization. - gen->set(index); - // If we start the alias list with the first entry, then we can process - // it unequivocally. - SymbolVector* aliasList = new SymbolVector(); - aliasList->push_back(sym); - aliases.insert(AliasMapElement(sym, aliasList)); - } + // Any function returning a value is considered to be a constructor. + Symbol* sym = se->var; + size_t index = symbolIndex.at(sym); + // We expect that each symbol gets constructed only once, so if we are + // about to set a bit in the gen set, it cannot already be true. + INT_ASSERT(gen->get(index) == false); + // If this assumption turns out to be false, it means we are reusing + // symbols. That case can be accommodated, but it means we have to + // insert a destructor call ahead of the symbol's reinitialization. + gen->set(index); } } } } -// Add the alias to the end of the alias list accessible through the original -// symbol, and then share the alias list withs the alias. -static void addAlias(Symbol* orig, Symbol* alias, - AliasVectorMap& aliases) +// Merge the alias lists of two symbols that have become aliases. +static void mergeAliases(Symbol* orig, Symbol* alias, + AliasVectorMap& aliases) { - SymbolVector* aliasList = aliases[orig]; - // Implicit assertion that aliasList is non-NULL. - aliasList->push_back(alias); - aliases.insert(AliasMapElement(alias, aliasList)); + SymbolVector* origList = aliases[orig]; + SymbolVector* aliasList = aliases[alias]; + for (size_t i = 0; i < aliasList->size(); ++i) + { + Symbol* sym = aliasList->at(i); + origList->push_back(sym); + } + delete aliasList; + aliases[alias] = origList; } @@ -282,8 +347,17 @@ static void processMove(CallExpr* call, SymExpr* se, BitVec* gen, AliasVectorMap size_t rindex = symbolIndex.at(rsym); // Copy ownership state from RHS. INT_ASSERT(gen->get(lindex) == false); - gen->copy(lindex, gen->get(rindex)); - addAlias(lsym, rsym, aliases); + if (!gen->get(rindex)) + { + if (fWarnOwnership) + USR_WARN(rsym, "Uninitialized symbol is copied here"); + } + else + { + gen->set(lindex); + } + // Merge aliases whether or not they are live. + mergeAliases(rsym, lsym, aliases); } } } @@ -299,8 +373,9 @@ static void processDestructor(SymExpr* se, // All members of an alias clique point to the same SymbolVector, so we only // need to look up one arbitrarily and then run the list. Symbol* sym = se->var; - SymbolVector& aliasList = *aliases[sym]; - for_vector(Symbol, alias, aliasList) + SymbolVector* aliasList = aliases[sym]; + + for_vector(Symbol, alias, *aliasList) { size_t index = symbolIndex.at(alias); // We expect a symbol to be live when it is killed. @@ -352,10 +427,16 @@ static void computeTransitions(SymExprVector& symExprs, { for_vector(SymExpr, se, symExprs) { + // We are only interested in local symbols, so if this one does not appear + // in our map, move on. + Symbol* sym = se->var; + if (symbolIndex.find(sym) == symbolIndex.end()) + continue; + // We are only interested in call expressions involving the SymExpr. if (CallExpr* call = toCallExpr(se->parentExpr)) { - processConstructor(call, se, gen, aliases, symbolIndex); + processConstructor(call, se, gen, symbolIndex); processMove(call, se, gen, aliases, symbolIndex); processDestructor(call, se, kill, aliases, symbolIndex); } @@ -420,6 +501,10 @@ static bool isJump(Expr* stmt) static void insertAutoDestroy(BasicBlock& bb, BitVec* to_kill, const SymbolVector& symbols) { + // Skip degenerate basic blocks. + if (bb.exprs.size() == 0) + return; + // Find the last statement in the block. Expr*& last = bb.exprs.back(); Expr* stmt = last->getStmtExpr(); @@ -464,7 +549,8 @@ static void insertAutoDestroy(FnSymbol* fn) SymbolVector symbols; SymbolIndexMap symbolIndex; - extractSymbols(fn, symbols, symbolIndex); + AliasVectorMap aliases; + extractSymbols(fn, symbols, symbolIndex, aliases); size_t size = symbols.size(); @@ -488,8 +574,6 @@ static void insertAutoDestroy(FnSymbol* fn) createFlowSet(IN, nbbs, size); createFlowSet(OUT, nbbs, size); - AliasVectorMap aliases; - computeTransitions(fn, GEN, KILL, aliases, symbolIndex); BasicBlock::forwardFlowAnalysis(fn, GEN, KILL, IN, OUT, true); @@ -509,6 +593,10 @@ static void insertAutoDestroy() { forv_Vec(FnSymbol, fn, gFnSymbols) { + // Function prototypes have no body, so we skip them. + if (fn->hasFlag(FLAG_FUNCTION_PROTOTYPE)) + continue; + insertAutoDestroy(fn); } } diff --git a/compiler/resolution/callDestructors.cpp b/compiler/resolution/callDestructors.cpp index f3945fa7d0b3..fc48a10addaf 100644 --- a/compiler/resolution/callDestructors.cpp +++ b/compiler/resolution/callDestructors.cpp @@ -761,7 +761,7 @@ fixupDestructors() { } } -#ifndef HILDE_MM + static void insertGlobalAutoDestroyCalls() { const char* name = "chpl__autoDestroyGlobals"; SET_LINENO(baseModule); @@ -782,7 +782,7 @@ static void insertGlobalAutoDestroyCalls() { } fn->insertAtTail(new CallExpr(PRIM_RETURN, gVoid)); } -#endif + static void insertDestructorCalls() { @@ -918,11 +918,10 @@ callDestructors() { cullExplicitAutoDestroyFlags(); insertAutoDestroyCalls(); returnRecordsByReferenceArguments(); +#else + insertAutoCopyAutoDestroy(); #endif insertYieldTemps(); -#ifndef HILDE_MM insertGlobalAutoDestroyCalls(); -#endif insertReferenceTemps(); -// insertAutoCopyAutoDestroy(); } From 607986cd60fd57ba60028f9d49c4b19dc2b42ea3 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Sun, 28 Dec 2014 16:53:06 -0600 Subject: [PATCH 08/53] Rework alias merge. Down to 449 regressions. --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index ac234f8fe4cd..846a97dd0808 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -318,9 +318,9 @@ static void mergeAliases(Symbol* orig, Symbol* alias, { Symbol* sym = aliasList->at(i); origList->push_back(sym); + aliases[sym] = origList; } delete aliasList; - aliases[alias] = origList; } From 71521e2ffeb39c2f96a2df049e40d5cc4f33059b Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Mon, 29 Dec 2014 01:02:01 -0600 Subject: [PATCH 09/53] Handle some special cases in alias tracking. If alias lists are the same, do not merge them again. The RHS symbol of a move is not necessarily a local symbol. It can be a module-level or extern symbol. Now down to 55 regresssions. --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index 846a97dd0808..6bf6619d0229 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -314,6 +314,13 @@ static void mergeAliases(Symbol* orig, Symbol* alias, { SymbolVector* origList = aliases[orig]; SymbolVector* aliasList = aliases[alias]; + + if (aliasList == origList) + { + // These two symbols are already aliases. + return; + } + for (size_t i = 0; i < aliasList->size(); ++i) { Symbol* sym = aliasList->at(i); @@ -344,7 +351,16 @@ static void processMove(CallExpr* call, SymExpr* se, BitVec* gen, AliasVectorMap Symbol* lsym = lhs->var; Symbol* rsym = rhs->var; size_t lindex = symbolIndex.at(lsym); - size_t rindex = symbolIndex.at(rsym); + SymbolIndexMap::const_iterator rsymPair = symbolIndex.find(rsym); + if (rsymPair == symbolIndex.end()) + { + // TODO: A move from a global or extern symbol without a copy-constructor + // call is probably an AST consistency or codegen error, given that + // we have already weeded out fundamental, extern and class types. + return; + } + size_t rindex = rsymPair->second; + // Copy ownership state from RHS. INT_ASSERT(gen->get(lindex) == false); if (!gen->get(rindex)) From 20512c5f75ea5056cb3aad5ca5c464dfe9ca42c4 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Mon, 29 Dec 2014 11:28:06 -0600 Subject: [PATCH 10/53] Move insertAutoCopyAutoDestroy pass after lowerIterators. Down to 46 regressions. --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 4 +++- compiler/resolution/callDestructors.cpp | 2 -- compiler/resolution/lowerIterators.cpp | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index 6bf6619d0229..d373d52541a8 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -297,7 +297,9 @@ static void processConstructor(CallExpr* call, SymExpr* se, size_t index = symbolIndex.at(sym); // We expect that each symbol gets constructed only once, so if we are // about to set a bit in the gen set, it cannot already be true. - INT_ASSERT(gen->get(index) == false); + if (gen->get(index)) + if (fWarnOwnership) + USR_WARN(sym, "Reinitialization of sym"); // If this assumption turns out to be false, it means we are reusing // symbols. That case can be accommodated, but it means we have to // insert a destructor call ahead of the symbol's reinitialization. diff --git a/compiler/resolution/callDestructors.cpp b/compiler/resolution/callDestructors.cpp index fc48a10addaf..385d7479fa39 100644 --- a/compiler/resolution/callDestructors.cpp +++ b/compiler/resolution/callDestructors.cpp @@ -918,8 +918,6 @@ callDestructors() { cullExplicitAutoDestroyFlags(); insertAutoDestroyCalls(); returnRecordsByReferenceArguments(); -#else - insertAutoCopyAutoDestroy(); #endif insertYieldTemps(); insertGlobalAutoDestroyCalls(); diff --git a/compiler/resolution/lowerIterators.cpp b/compiler/resolution/lowerIterators.cpp index 9b46ae7b4ffc..30b95a598a83 100644 --- a/compiler/resolution/lowerIterators.cpp +++ b/compiler/resolution/lowerIterators.cpp @@ -2022,5 +2022,9 @@ void lowerIterators() { reconstructIRautoCopyAutoDestroy(); cleanupTemporaryVectors(); + +#ifdef HILDE_MM + insertAutoCopyAutoDestroy(); +#endif } From 188ec9e1d05fce8f32ab0e0b29b908962b81958a Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Wed, 31 Dec 2014 02:27:43 -0600 Subject: [PATCH 11/53] Add typedefs to the head of bb.h. I thought I would need these but I don't. But this follows the "single point of definition" ROT, so I kept it. --- compiler/include/bb.h | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/compiler/include/bb.h b/compiler/include/bb.h index 42735d46dba2..f85169c0f30a 100644 --- a/compiler/include/bb.h +++ b/compiler/include/bb.h @@ -36,6 +36,13 @@ class SymExpr; // The goto and label maps persist only between calls to buildBasicBlocks. class BasicBlock { + // + // Typedefs + // + public: + typedef std::vector BasicBlockVector; + typedef std::vector BitVecVector; + // // Class methods/variables // @@ -51,16 +58,16 @@ class BasicBlock Map& localMap); static void backwardFlowAnalysis(FnSymbol* fn, - std::vector& GEN, - std::vector& KILL, - std::vector& IN, - std::vector& OUT); + BitVecVector& GEN, + BitVecVector& KILL, + BitVecVector& IN, + BitVecVector& OUT); static void forwardFlowAnalysis (FnSymbol* fn, - std::vector& GEN, - std::vector& KILL, - std::vector& IN, - std::vector& OUT, + BitVecVector& GEN, + BitVecVector& KILL, + BitVecVector& IN, + BitVecVector& OUT, bool intersect = true); static void printLocalsVector(Vec locals, @@ -69,10 +76,10 @@ class BasicBlock static void printDefsVector(std::vector defs, Map& defMap); - static void printLocalsVectorSets(std::vector& sets, + static void printLocalsVectorSets(BitVecVector& sets, Vec locals); - static void printBitVectorSets(std::vector& sets); + static void printBitVectorSets(BitVecVector& sets); static BasicBlock* basicBlock; @@ -80,7 +87,7 @@ class BasicBlock BasicBlock*> labelMaps; static Map*> gotoMaps; + BasicBlockVector*> gotoMaps; private: static void buildBasicBlocks(FnSymbol* fn, @@ -109,8 +116,8 @@ class BasicBlock std::vector exprs; std::vector marks; - std::vector ins; - std::vector outs; + BasicBlockVector ins; + BasicBlockVector outs; private: bool isOK(); From 7a592ecfb557477429a6f28fcbe08cb2ab962c2c Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Wed, 31 Dec 2014 02:29:28 -0600 Subject: [PATCH 12/53] Added operator synonyms for conjunction (&=) and disjunction (|=). --- compiler/include/bitVec.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/include/bitVec.h b/compiler/include/bitVec.h index b77ea4fd0dc5..fe4cd7d3bba3 100644 --- a/compiler/include/bitVec.h +++ b/compiler/include/bitVec.h @@ -36,7 +36,10 @@ class BitVec { void disjunction(BitVec& other); void intersection(BitVec& other); - + // Synonyms for disjunction (union) and (conjunction) intersection above. + void operator|=(BitVec& other) { this->disjunction(other); } + void operator&=(BitVec& other) { this->intersection(other); } + // Added functionality to make this compatible with std::bitset and thus // boosts dynamic bitset if that gets into the STL, or we start using boost bool equals(BitVec& other); From 8dfa029c305e394270032c07ac64c5f92a43d62b Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Wed, 31 Dec 2014 02:32:21 -0600 Subject: [PATCH 13/53] Added back "insert auto copy" flags in resolution. --- compiler/resolution/functionResolution.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/resolution/functionResolution.cpp b/compiler/resolution/functionResolution.cpp index 4441c0e5424e..dce255728a3f 100644 --- a/compiler/resolution/functionResolution.cpp +++ b/compiler/resolution/functionResolution.cpp @@ -5483,17 +5483,17 @@ postFold(Expr* expr) { } } if (!set) { -#ifndef HILDE_MM if (lhs->var->hasFlag(FLAG_EXPR_TEMP) && !lhs->var->hasFlag(FLAG_TYPE_VARIABLE)) { if (CallExpr* rhsCall = toCallExpr(call->get(2))) { if (requiresImplicitDestroy(rhsCall)) { lhs->var->addFlag(FLAG_INSERT_AUTO_COPY); +#ifndef HILDE_MM lhs->var->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif } } } -#endif if (isReferenceType(lhs->var->type) || lhs->var->type->symbol->hasFlag(FLAG_REF_ITERATOR_CLASS) || lhs->var->type->symbol->hasFlag(FLAG_ARRAY)) From 0b28ae86f1f74d0f3310d53ed37d647c0f945c7c Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Wed, 31 Dec 2014 02:33:37 -0600 Subject: [PATCH 14/53] Moved insertAutoCopyAutoDestroy pass to start of parallel. --- compiler/passes/parallel.cpp | 12 ++++++++++++ compiler/resolution/lowerIterators.cpp | 4 ---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/compiler/passes/parallel.cpp b/compiler/passes/parallel.cpp index 6456dc8ba440..cc6fccbc4f1c 100644 --- a/compiler/passes/parallel.cpp +++ b/compiler/passes/parallel.cpp @@ -1100,6 +1100,18 @@ reprivatizeIterators() { void parallel(void) { + +#ifdef HILDE_MM + // This is here just because it depends on the cleanup after lowerIterators + // having been performed, and it depends on lower iterators to get the basic + // block structure of iterator functions right. + // In other words, it could be moved back to the end of callDestructors.cpp + // if basic block analysis were modified to treat a PRIM_YIELD as an + // end-of-block and treat all blocks within an iterator function as + // successors of a faked-in start block. + insertAutoCopyAutoDestroy(); +#endif + Vec taskFunctions; // Collect the task functions for processing. diff --git a/compiler/resolution/lowerIterators.cpp b/compiler/resolution/lowerIterators.cpp index 30b95a598a83..9b46ae7b4ffc 100644 --- a/compiler/resolution/lowerIterators.cpp +++ b/compiler/resolution/lowerIterators.cpp @@ -2022,9 +2022,5 @@ void lowerIterators() { reconstructIRautoCopyAutoDestroy(); cleanupTemporaryVectors(); - -#ifdef HILDE_MM - insertAutoCopyAutoDestroy(); -#endif } From 948ac19445b2560a034d8769aed98a0c520bdf2e Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Wed, 31 Dec 2014 02:38:11 -0600 Subject: [PATCH 15/53] Now actually adding autoDestroy calls. Added a backward flow pass to propagate disharge-of-ownership constraints from IN sets back across the gap to OUT sets. The OUT sets of terminal blocks are set to all zeroes to force all locals to be autodestroyed on exit. Added code so that we only add an autoDestroy for one member of an alias clique. --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 96 +++++++++++++++++-- 1 file changed, 86 insertions(+), 10 deletions(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index d373d52541a8..8a360fec7b39 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -20,6 +20,10 @@ // TODO: This should be moved to symbol.cpp bool fWarnOwnership = false; +// TODO: Remove this #define for normal compilation +#define DEBUG_AMM 1 +static int debug = 0; + // insertAutoCopyAutoDestroy // // Inserts copy-constructor and destructor calls as needed, by tracking the @@ -67,14 +71,15 @@ bool fWarnOwnership = false; //# - A record field in a record. //# If ownership of any of these is false when they (or their container) is //# about to go out of scope, a copy must be made to bring ownership to the -//# expected state. Otherwise, ownership can be shared and a copy potentially saved. +//# expected state. Otherwise, ownership can be shared and a copy potentially +//# avoided. //# //# After copy-constructor calls have been added, the routine has its full //# complement of construcotr calls. Correct AMM is then just a matter of //# inserting the minimum number of destructor calls to drive the ownership of //# all local variables to false before the routine ends. Dataflow analysis is //# used to propagate this constraint backwards, in case ownership is -//# transferred out of a variable along one path but not another. +//# transferred out of a block along one path but not another. //# //############################################################################# @@ -118,8 +123,8 @@ bool fWarnOwnership = false; // all successors of block i. (No information is propagated backward through // the blocks, so this can be done in one iteration.) Then, in the local // traversal the follows, for any symbol that remains owned at the end of block -// i where its OUT[i] is false, we add a destructor call to make that condition -// true. +// i where its OUT[i] is false, we add a destructor call so that the results of +// forward and backward flow agree. // #include "passes.h" // The main routine is declared here. @@ -130,6 +135,7 @@ bool fWarnOwnership = false; #include "bitVec.h" #include "stlUtil.h" #include "astutil.h" +#include "view.h" // For list_view (debugging). #include "resolution.h" // For AutoDestroyMap. #include @@ -391,7 +397,7 @@ static void processDestructor(SymExpr* se, // All members of an alias clique point to the same SymbolVector, so we only // need to look up one arbitrarily and then run the list. Symbol* sym = se->var; - SymbolVector* aliasList = aliases[sym]; + SymbolVector* aliasList = aliases.at(sym); for_vector(Symbol, alias, *aliasList) { @@ -498,6 +504,34 @@ static void computeTransitions(FnSymbol* fn, } +// In backward flow, we adjust the out set so it is the intersection of the IN +// sets of its successors. If the block is a terminal block (a block with no +// successors), however, we set its OUT to all zeroes. This forces ownership to +// be driven to zero before the function is exited. +// This analysis will work correctly even if the function has multiple exits. +static void backwardFlowOwnership(const BasicBlock::BasicBlockVector& bbs, + FlowSet& IN, FlowSet& OUT) +{ + size_t nbbs = bbs.size(); + for (size_t i = 0; i < nbbs; ++i) + { + if (bbs[i]->outs.size() == 0) + { + // This is a terminal block because it has no successors. + // Its OUT set must be empty. + OUT[i]->clear(); + } + else + { + BitVec* out = OUT[i]; + out->set(); + for_vector(BasicBlock, succ, bbs[i]->outs) + *out &= *IN[succ->id]; + } + } +} + + // This predicate determines if the given statement is a jump. static bool isJump(Expr* stmt) { @@ -517,7 +551,9 @@ static bool isJump(Expr* stmt) // At the end of this basic block, insert an autodestroy for each symbol // specified by the given bit-vector. static void insertAutoDestroy(BasicBlock& bb, BitVec* to_kill, - const SymbolVector& symbols) + const SymbolVector& symbols, + const SymbolIndexMap& symbolIndex, + const AliasVectorMap& aliases) { // Skip degenerate basic blocks. if (bb.exprs.size() == 0) @@ -529,24 +565,52 @@ static void insertAutoDestroy(BasicBlock& bb, BitVec* to_kill, bool isjump = isJump(stmt); // For each true bit in the bit vector, add an autodestroy call. + // But destroying one member of an alias list destroys them all. + SET_LINENO(stmt); for (size_t j = 0; j < to_kill->size(); ++j) { if (to_kill->get(j)) { Symbol* sym = symbols[j]; - CallExpr* autoDestroyCall = new CallExpr(autoDestroyMap.get(sym->type), sym); + + // Remove this symbol and all its aliases from the kill set. + SymbolVector* aliasList = aliases.at(sym); + for_vector(Symbol, alias, *aliasList) + { + size_t index = symbolIndex.at(alias); + to_kill->reset(index); + } + + FnSymbol* autoDestroy = toFnSymbol(autoDestroyMap.get(sym->type)); + if (autoDestroy == NULL) + // This type does not have a destructor, so we don't have a add an + // autoDestroy call for it. + continue; + + // For now, we ignore internally reference-counted types. We'll add the + // code to manage those later. + if (isRefCountedType(sym->type)) + continue; + + CallExpr* autoDestroyCall = new CallExpr(autoDestroy, sym); + if (isjump) stmt->insertBefore(autoDestroyCall); else stmt->insertAfter(autoDestroyCall); + + insertReferenceTemps(autoDestroyCall); } } } + static void insertAutoDestroy(FnSymbol* fn, FlowSet& GEN, FlowSet& KILL, FlowSet& IN, FlowSet& OUT, - const SymbolVector& symbols) + const SymbolVector& symbols, + const SymbolIndexMap& symbolIndex, + const AliasVectorMap& aliases) { size_t nbbs = fn->basicBlocks->size(); for (size_t i = 0; i < nbbs; i++) @@ -555,11 +619,13 @@ static void insertAutoDestroy(FnSymbol* fn, // (live) at the end of the block but is unowned (dead) in the OUT set. BasicBlock& bb = *(*fn->basicBlocks)[i]; BitVec* to_kill = *IN[i] + *GEN[i] - *KILL[i] - *OUT[i]; - insertAutoDestroy(bb, to_kill, symbols); + insertAutoDestroy(bb, to_kill, symbols, symbolIndex, aliases); delete to_kill; to_kill = 0; } } + +// Insert autoDestroy calls in a function as needed. static void insertAutoDestroy(FnSymbol* fn) { BasicBlock::buildBasicBlocks(fn); @@ -594,9 +660,19 @@ static void insertAutoDestroy(FnSymbol* fn) computeTransitions(fn, GEN, KILL, aliases, symbolIndex); + // We can reuse the existing forward flow analysis to compute how ownership + // flows through the basic blocks. BasicBlock::forwardFlowAnalysis(fn, GEN, KILL, IN, OUT, true); - insertAutoDestroy(fn, GEN, KILL, IN, OUT, symbols); + // We need our own equation for backward flow. + // Backward flow determines where ownership must be given up through a + // delete, by making the OUT set the AND of all its successor INs and the IN + // be no greater than OUT | KILL (that is, every symbol owned at the + // beginning of the block (IN) must either appear in OUT or be killed in the + // block. + backwardFlowOwnership(*fn->basicBlocks, IN, OUT); + + insertAutoDestroy(fn, GEN, KILL, IN, OUT, symbols, symbolIndex, aliases); destroyAliasVectorMap(aliases); From 13416fa94821bc6f2422245b50e87a27236de5a5 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Thu, 1 Jan 2015 15:11:23 -0600 Subject: [PATCH 16/53] Add autoCopy on const temps; mark return of PRIM_DEREF as unowned. Down to 1475 regressions. --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 10 ++++++++++ compiler/passes/normalize.cpp | 5 ++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index 8a360fec7b39..279f45b69dda 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -278,6 +278,16 @@ static bool isConstructor(CallExpr* call) if (AggregateType* at = toAggregateType(call->typeInfo())) if (at->isClass()) return false; + + switch (call->primitive->tag) + { + default: + break; // Fall through and return true. + case PRIM_DEREF: + // Because the operand is a reference, its contents are "unowned" by the + // result. An autoCopy needs to be inserted where an owned copy is required. + return false; + } } return true; diff --git a/compiler/passes/normalize.cpp b/compiler/passes/normalize.cpp index 20a0e0ca8949..78db404a11f3 100644 --- a/compiler/passes/normalize.cpp +++ b/compiler/passes/normalize.cpp @@ -915,7 +915,10 @@ fix_def_expr(VarSymbol* var) { if (var->hasFlag(FLAG_CONST) && !var->hasEitherFlag(FLAG_EXTERN, FLAG_REF_VAR)) { constTemp = newTemp("const_tmp"); stmt->insertBefore(new DefExpr(constTemp)); - stmt->insertAfter(new CallExpr(PRIM_MOVE, var, constTemp)); + // Temporary hack: Add an autoCopy here, because the var is being + // initialized with the RHS value. + // Later, this insertion will be handled parsimoniously by insertAutoCopyAutoDestroy. + stmt->insertAfter(new CallExpr(PRIM_MOVE, var, new CallExpr("chpl__autoCopy", constTemp))); } // From ab0d5df0933c933f48844c911d07a3ea3d8a373a Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Mon, 5 Jan 2015 17:32:37 -0600 Subject: [PATCH 17/53] Add insertion of autoCopy calls. Rewrite header comments for improved accuracy. Factor out AliasVectorMap manipulations into a class. Factor out common code between gen/kill set creation and autoCopy insertion. Move predicates up toward top of file. Renamed predicates to better indicate how they relate to ownership rather than toward code structure. Moved autoCopy insertion pass up, right after forward flow analysis. [Tested with hello.chpl] --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 746 ++++++++++++------ 1 file changed, 492 insertions(+), 254 deletions(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index 279f45b69dda..397a1484d592 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -26,11 +26,11 @@ static int debug = 0; // insertAutoCopyAutoDestroy // -// Inserts copy-constructor and destructor calls as needed, by tracking the -// "ownership" of record objects. -// //############################################################################# //# +//# Inserts copy-constructor and destructor calls as needed, by tracking the +//# "ownership" of record objects. +//# //# Ownership represents the "liveness" of a record object in the sense that it //# may contain heap-allocated data. After a record object is constructed, any //# of its fields of class type may refer to a class object. In that state, any @@ -40,9 +40,6 @@ static int debug = 0; //# the destructor is called, the record variable no longer owns any //# heap-allocated data, so that data is "unowned". //# -//# In the simplest view, ownership is a binary state: construction changes the -//# state from false to true; destruction changes the state from true to false. -//# //# Ownership of a record can also be transferred by a bit-wise copy. When a //# bit-wise copy of a record is made, class fields (which are implemented as //# pointers) are copied verbatim. That means that both record objects point to @@ -65,9 +62,10 @@ static int debug = 0; //# We assume that the input AST contains the minimum number of //# copy-constructor calls required to provide the specified semantics. That //# is, record values passed by in intent must be copied. We do not yet -//# attempt to pass ownership of an object into a called routine. Two kinds of -//# variable whose ownersihp must be true before exiting a routine are: +//# attempt to pass ownership of an object into a called routine. +//# Variables whose ownersihp must be true before exiting a routine are: //# - The return value variable +//# - A module-level variable. //# - A record field in a record. //# If ownership of any of these is false when they (or their container) is //# about to go out of scope, a copy must be made to bring ownership to the @@ -77,54 +75,75 @@ static int debug = 0; //# After copy-constructor calls have been added, the routine has its full //# complement of construcotr calls. Correct AMM is then just a matter of //# inserting the minimum number of destructor calls to drive the ownership of -//# all local variables to false before the routine ends. Dataflow analysis is -//# used to propagate this constraint backwards, in case ownership is -//# transferred out of a block along one path but not another. +//# all local variables to false before the routine ends. //# //############################################################################# +// This pass is applied to each function in turn. The algorithm has four main +// steps: +// 1. Gather local (argument and variable) symbols and build a vector and index +// of these. +// 2. Traverse each block to populate the GEN and KILL sets and the alias map. +// 3. Perform forward flow analysis to determine which symbols are owned at the +// beginning of each block. Traverse each block again and add autoCopy +// calls so symbols passed to receptor arguments are always owned. +// 4. Perform backward flow analysis to determine which symbols must be unowned +// by the end of each block. Add autoDestroy calls at the end of each block +// for symbols that are owned at the end of the block but must be unowned +// before the block is exited. +// +// Dataflow analysis is used to propagate this constraint backwards, in case +// ownership is transferred out of a block along one path but not another. // The sets used for flow analysis are: -// GEN -- The set of symbols whose ownership transitions to true by the end of +// GEN -- The set of symbols whose ownership transitions to true by the end of +// the block. +// KILL -- The set of symbols whose ownership transitions to false anywhere in // the block. -// KILL -- The set of symbols whose ownership transitions to false by the end -// of the block. -// IN -- The set of symbols that are owned at the beginning of the block. -// OUT -- The set of symbols that are owned at the end of the block. +// IN -- The set of symbols that are owned at the beginning of the block +// (after forward flow analysis). +// OUT -- The set of symbols that are owned at the end of the block (after +// forward flow analysis); the set of symbols that must not be owned +// at the end of the block (after backward flow analysis). // // GEN[i] contains a true bit corresponding to each symbol that is constructed // in that block. We expect that symbol is constructed only once, so if IN[i] // and GEN[i] have a true bit in the same position, we can flag an internal // error. This condition is unlikely, so at best it should be added as a -// verify check. +// verify check. After forward flow analysis, GEN[i] is initialized to IN[i] +// to show that ownership has reached this block from another // -// KILL[i] contains a true bit corresponding to each symbol that is destroyed -// or whose ownership is usurped in that block. We keep a separate list of -// aliases. When a bitwise copy is made, we treat the new copy as if it has -// been constructed without changing the state of the original. When any -// member of that set of aliases is destroyed or usurped, all members of the -// set are added to KILL[i]. +// KILL[i] contains a true bit corresponding to each symbol whose ownership is +// consumed in that block. We keep a separate list of aliases. When any +// member of that set of aliases is consumed, all members of the set are added +// to KILL[i]. // -// We start by performing forward flow analysis to establish the value of IN. +// We start by performing forward flow analysis to establish the value of IN. +// Within each block, OUT[i] <- IN[i] - KILL[i] + GEN[i]. // IN[i] is the intersection of OUT[pred(i)] for all predecessors of block i. -// It is desirable to allow ownership to flow through to the end of the -// function (to a final block with many predecessors). That way, we can -// consume liveness with a minimum of destructor calls. However, if we cannot -// depend on a given symbol being owned at the beginning of a block, then its -// destructor must be pushed back up the flow graph. For example, we might -// pass ownership to a field or global variable on one path; then we need a -// destructor call to get rid of ownership on the other path(s). // -// For a block that contains a constructor followed by a destructor, both -// GEN[i] and KILL[i] will be zero. If they are both true, that represents a -// symbol being destroyed and then reused, so GEN takes precedence over KILL -// in forward flow: OUT[i] = IN[i] - KILL[i] + GEN[i]. +// For a block that contains a constructor followed by a destructor for the +// same symbol j, GEN[i,j] will be false and KILL[i,j] will be true. +// For a block that contains a destructor followed by a constructor for the +// same symbol j, GEN[i,j] and KILL[i,j] will both be true. // // For backward flow, we compute OUT[i] as the intersection of IN[succ(i)] for // all successors of block i. (No information is propagated backward through -// the blocks, so this can be done in one iteration.) Then, in the local -// traversal the follows, for any symbol that remains owned at the end of block -// i where its OUT[i] is false, we add a destructor call so that the results of -// forward and backward flow agree. +// the blocks, so this can be done in one iteration.) +// +// After forward flow analysis, we traverse blocks again and add autoCopy calls +// where a symexpr is unowned (at that point in the flow) but being passed to +// an argument that consumes it. (Consumers expect the corresonding operand to +// be owned; the autoCopy call adds the required ownership.) Note that adding +// an autoCopy to an operand does not change GEN[i] or KILL[i], so the results +// of forward flow do not have to be recomputed after this pass. +// +// For any symbol that remains owned at the end of block i where its OUT[i] is +// false, we add a destructor call so that the results of forward and backward +// flow agree. Each added destructor call effectively sets the corresponding +// KILL[i,j] and clears the corresponding GEN[i,j]. If we update these flow +// sets during this pass, then we could check IN[i] - KILL[i] + GEN[i] == +// OUT[i]. But since we are iterating on the difference between these two +// bitsets (i.e. OUT - (IN - KILL + GEN)), the test becomes a tautology. // #include "passes.h" // The main routine is declared here. @@ -145,8 +164,6 @@ typedef std::vector FlowSet; typedef std::vector SymbolVector; typedef std::map SymbolIndexMap; typedef SymbolIndexMap::value_type SymbolIndexElement; -typedef std::map AliasVectorMap; -typedef AliasVectorMap::value_type AliasMapElement; typedef std::vector SymExprVector; typedef std::vector DefExprVector; @@ -164,33 +181,106 @@ static void createFlowSet(std::vector& set, static void destroyFlowSet(std::vector set) { for_vector(BitVec, vec, set) - delete vec, vec = 0; + delete vec; vec = 0; } -// An alias vector map is map from symbols to lists of symbols, but all of the -// symbols in the same clique share the same list. So, to destroy all the -// lists in the map, we find the list through the first member of the clique, -// then set the map element for each member in the list to NULL. (The list -// contains the element though which the list is accessed, so this detaches the -// list from all map elements.) Then we delete the list. (The symbols -// contained in the list are owned elsewhere.) -static void destroyAliasVectorMap(AliasVectorMap& aliases) +//############################## AliasVectorMap ############################## +//# +//# A utility class derived from std::map. +//# An element in this map can contain dynamically-allocated memory through its +//# SymbolVector (second) field. So the main point of the class is to provide +//# a destructor that will clean up an instance properly. +//# The insert and merge utilities manipulate the data structure while +//# maintaining its main invariants: +//# 1. Each member maps a symbol to a vector of symbols. +//# 2. The vector of symbols contains at least the same symbol and also other +//# symbols with which it is an alias. +//# 3. The set of mutual aliases forms a clique. Since this set is common to +//# all its members, the symbol vector representing a clique is shared among all +//# members belonging to that clique. +//# +//######################################################################## + +class AliasVectorMap : public std::map +{ + public: + typedef value_type AliasMapElement; + + private: + // Copying an alias vector map involves copying the symbol vector -- which we + // don't want to do, so we just disallow copying instead. + AliasVectorMap(const AliasVectorMap& rhs); + void operator=(const AliasVectorMap& rhs); + + public: + ~AliasVectorMap(); + AliasVectorMap() : std::map() {} + + void insert(Symbol* sym); + void merge(Symbol* orig, Symbol* alias); +}; + +// To destroy all the lists in the map, we find the list through the first +// member of the clique, then set the map element for each member in the list +// to NULL. Then we delete the list. +AliasVectorMap::~AliasVectorMap() { - for (AliasVectorMap::iterator it = aliases.begin(); - it != aliases.end(); ++it) + for (iterator it = begin(); + it != end(); ++it) { if (it->second == NULL) + // This entry is part of a clique that was already deleted. continue; SymbolVector* aliasList = it->second; for_vector(Symbol, sym, *aliasList) - aliases[sym] = NULL; + at(sym) = NULL; delete aliasList; } } +// Insert a symbol into the alias vector map. +// When a symbol is first inserted it is associated with an alias vector that +// contains only itself. +void +AliasVectorMap::insert(Symbol* sym) +{ + SymbolVector* aliasList = new SymbolVector(); + aliasList->push_back(sym); + operator[](sym) = aliasList; +} + +// Merge the alias lists of two symbols that have become aliases. +void +AliasVectorMap::merge(Symbol* orig, Symbol* alias) +{ + SymbolVector* origList = at(orig); + SymbolVector* aliasList = at(alias); + + if (aliasList == origList) + { + // These two symbols are already aliases. + return; + } + + // Copy elements from the alias's list into the original symbol's list and + // set each entry in the map corresponding to those elements to point to the + // updated original list. + for (size_t i = 0; i < aliasList->size(); ++i) + { + Symbol* sym = aliasList->at(i); + origList->push_back(sym); + operator[](sym) = origList; + } + + // After that, no elements point to this aliasList, so it is deleted. + delete aliasList; +} + +//######################### End of AliasVectorMap ######################### + // Scans the body of the given function and inserts all of the variable and // argument symbols defined in it into the vector of symbols. Bits in the flow @@ -198,8 +288,6 @@ static void destroyAliasVectorMap(AliasVectorMap& aliases) // Also constructs an index map, to make it easier to find the index of a // symbol in the vector. (Otherwise, a linear search is required.) // The alias map can also be populated at the same time. -// TODO: Split the alias list off as a separate class, mostly to make it easy -// to document the data structure it implements. static void extractSymbols(FnSymbol* fn, SymbolVector& symbols, @@ -251,206 +339,264 @@ extractSymbols(FnSymbol* fn, INT_ASSERT(symbols[symbolIndex[sym]] == sym); // Initialize each entry in the alias map with a list of symbols - // containingh the symbol itself. - SymbolVector* aliasList = new SymbolVector(); - aliasList->push_back(sym); - aliases.insert(AliasMapElement(sym, aliasList)); + // containing just the symbol itself. + aliases.insert(sym); } } -// Returns true if this function returns a fully-constructed value; false otherwise. -static bool isConstructor(CallExpr* call) -{ - if (FnSymbol* fn = call->isResolved()) - { - // A "normal" function. +//############################## Predicates ############################## +//# - // Return values of class type are ruled out. - Type* retType = fn->retType; - if (AggregateType* at = toAggregateType(retType)) - if (at->isClass()) - return false; - } - else +// Returns true if the given SymExpr is contructed by the expression in which it +// appears; false otherwise. +static bool isCreated(SymExpr* se) +{ + if (CallExpr* call = toCallExpr(se->parentExpr)) { - // A primitive. - if (AggregateType* at = toAggregateType(call->typeInfo())) - if (at->isClass()) + // In the current incarnation, we expect construction to look like: + // ('move' lhs (construct )) + // When constructors turn into methods, this will look a bit different. + if (call->isPrimitive(PRIM_MOVE) || + call->isPrimitive(PRIM_ASSIGN)) + { + // We expect the target symbol to appear as the first operand. + if (call->get(1) != se) return false; - switch (call->primitive->tag) - { - default: - break; // Fall through and return true. - case PRIM_DEREF: - // Because the operand is a reference, its contents are "unowned" by the - // result. An autoCopy needs to be inserted where an owned copy is required. - return false; + if (CallExpr* rhsCall = toCallExpr(call->get(2))) + { + if (FnSymbol* fn = rhsCall->isResolved()) + { + // A "normal" function. + + // Return values of class type are ruled out. + Type* retType = fn->retType; + if (AggregateType* at = toAggregateType(retType)) + if (at->isClass()) + return false; + } + else + { + // A primitive. + + // We are only interested in record types, so if the RHS is not of + // aggregate type, we can skip it. + if (AggregateType* at = toAggregateType(rhsCall->typeInfo())) + if (at->isClass()) + return false; + + switch (rhsCall->primitive->tag) + { + default: + break; // Fall through and return true. + case PRIM_DEREF: + // Because the operand is a reference, its contents are "unowned" by the + // result. An autoCopy needs to be inserted where an owned copy is required. + return false; + } + } + return true; + } } } - return true; + // Return false by default. + return false; } -static void processConstructor(CallExpr* call, SymExpr* se, - BitVec* gen, - const SymbolIndexMap& symbolIndex) +// Returns true if the expression in which the given SymExpr appears makes it a +// bitwise copy of some other symbol; false otherwise. +static bool isBitwiseCopy(SymExpr* se) { - // In the current incarnation, we expect construction to look like: - // ('move' lhs (construct )) - // When constructors turn into methods, this will look a bit different. - if (call->isPrimitive(PRIM_MOVE) || - call->isPrimitive(PRIM_ASSIGN)) + // Must be a call (as opposed to a DefExpr or LabelExpr, etc. + if (CallExpr* call = toCallExpr(se->parentExpr)) { - if (CallExpr* rhsCall = toCallExpr(call->get(2))) + // Call must be a move or assignment + if (call->isPrimitive(PRIM_MOVE) || + call->isPrimitive(PRIM_ASSIGN)) { - if (isConstructor(rhsCall)) + // se must be the first argument. + if (call->get(1) == se) { - // Any function returning a value is considered to be a constructor. - Symbol* sym = se->var; - size_t index = symbolIndex.at(sym); - // We expect that each symbol gets constructed only once, so if we are - // about to set a bit in the gen set, it cannot already be true. - if (gen->get(index)) - if (fWarnOwnership) - USR_WARN(sym, "Reinitialization of sym"); - // If this assumption turns out to be false, it means we are reusing - // symbols. That case can be accommodated, but it means we have to - // insert a destructor call ahead of the symbol's reinitialization. - gen->set(index); + // The second argument must also be a symexpr. + if (isSymExpr(call->get(2))) + return true; } } } + return false; } -// Merge the alias lists of two symbols that have become aliases. -static void mergeAliases(Symbol* orig, Symbol* alias, - AliasVectorMap& aliases) +// Returns true if the expression in which the given SymExpr appears causes +// its ownership to be consumed; false otherwise. +static bool isConsumed(SymExpr* se) { - SymbolVector* origList = aliases[orig]; - SymbolVector* aliasList = aliases[alias]; - - if (aliasList == origList) + if (CallExpr* call = toCallExpr(se->parentExpr)) { - // These two symbols are already aliases. - return; - } - - for (size_t i = 0; i < aliasList->size(); ++i) - { - Symbol* sym = aliasList->at(i); - origList->push_back(sym); - aliases[sym] = origList; - } - delete aliasList; -} - + if (FnSymbol* fn = call->isResolved()) + { + // This is a function call. -static void processMove(CallExpr* call, SymExpr* se, BitVec* gen, AliasVectorMap& aliases, - const SymbolIndexMap& symbolIndex) -{ - // We only care about bitwise copies here. - if (call->isPrimitive(PRIM_MOVE) || - call->isPrimitive(PRIM_ASSIGN)) - { - // We'll key off the LHS. - // Meaning that we will only pay attention to the expression if the SymExpr - // is in the LHS position in the primitive. - if (SymExpr* lhs = toSymExpr(call->get(1))) + // The only one we're interested in right now is a destructor call. + if (fn->hasFlag(FLAG_DESTRUCTOR)) + { + // Paranoid check: This SymExpr is the thing being destroyed, right? + INT_ASSERT(call->get(1) == se); + return true; + } + } + else { - if (lhs == se) + // This is a primitive. + if (call->isPrimitive(PRIM_RETURN)) + { + // Returns act like destructors. + return true; + } +#if 0 + // Not yet. + if (call->isPrimitive(PRIM_MOVE)) { - // We only care about bitwise copies from one symbol to another. - if (SymExpr* rhs = toSymExpr(call->get(2))) + // If the left side of a move is a global, it assumes ownership from the + // RHS. We can assume that the RHS is local. + Expr* lhs = call->get(1); + if (SymExpr* lhse = toSymExpr(lhs)) { - Symbol* lsym = lhs->var; - Symbol* rsym = rhs->var; - size_t lindex = symbolIndex.at(lsym); - SymbolIndexMap::const_iterator rsymPair = symbolIndex.find(rsym); - if (rsymPair == symbolIndex.end()) - { - // TODO: A move from a global or extern symbol without a copy-constructor - // call is probably an AST consistency or codegen error, given that - // we have already weeded out fundamental, extern and class types. - return; - } - size_t rindex = rsymPair->second; - - // Copy ownership state from RHS. - INT_ASSERT(gen->get(lindex) == false); - if (!gen->get(rindex)) - { - if (fWarnOwnership) - USR_WARN(rsym, "Uninitialized symbol is copied here"); - } - else - { - gen->set(lindex); - } - // Merge aliases whether or not they are live. - mergeAliases(rsym, lsym, aliases); + if (lhse->var->type->symbol->hasFlag(FLAG_REF) || + lhse->var->defPoint->parentSymbol->hasFlag(FLAG_MODULE_INIT)) + return true; } } +#endif } } + + return false; } -static void processDestructor(SymExpr* se, - BitVec* kill, AliasVectorMap& aliases, - const SymbolIndexMap& symbolIndex) +// This predicate determines if the given statement is a jump. +static bool isJump(Expr* stmt) { - // Add all members of an alias clique to the kill set. - // All members of an alias clique point to the same SymbolVector, so we only - // need to look up one arbitrarily and then run the list. + // A goto is definitely a jump. + if (isGotoStmt(stmt)) + return true; + + // A return primitive works like a jump. (Nothing should appear after it.) + if (CallExpr* call = toCallExpr(stmt)) + if (call->isPrimitive(PRIM_RETURN)) + return true; + + return false; +} + + +static void processCreator(SymExpr* se, + BitVec* gen, + const SymbolIndexMap& symbolIndex) +{ + // Any function returning a value is considered to be a constructor. Symbol* sym = se->var; - SymbolVector* aliasList = aliases.at(sym); + size_t index = symbolIndex.at(sym); + + // We expect that each symbol gets constructed only once, so if we are + // about to set a bit in the gen set, it cannot already be true. + // If this assumption turns out to be false, it means we are reusing + // symbols. That case can be accommodated, but it means we should + // insert a destructor call ahead of the symbol's reinitialization. + if (gen->get(index)) + if (fWarnOwnership) + USR_WARN(sym, "Reinitialization of sym"); + + gen->set(index); +} - for_vector(Symbol, alias, *aliasList) + +static void processBitwiseCopy(SymExpr* se, BitVec* gen, + const SymbolIndexMap& symbolIndex) +{ + CallExpr* call = toCallExpr(se->parentExpr); + + SymExpr* lhs = toSymExpr(call->get(1)); + SymExpr* rhs = toSymExpr(call->get(2)); + + Symbol* lsym = lhs->var; + Symbol* rsym = rhs->var; + + size_t lindex = symbolIndex.at(lsym); + SymbolIndexMap::const_iterator rsymPair = symbolIndex.find(rsym); + if (rsymPair == symbolIndex.end()) { - size_t index = symbolIndex.at(alias); - // We expect a symbol to be live when it is killed. - INT_ASSERT(kill->get(index) == false); - kill->set(index); + // TODO: A move from a global or extern symbol without a copy-constructor + // call is probably an AST consistency or codegen error, given that + // we have already weeded out fundamental, extern and class types. + return; } + size_t rindex = rsymPair->second; - // We don't bother updating the alias list under the assumption that - // symbols are not reused. If that turns out to be false, we have to - // remove the alias clique from aliases here. + // Copy ownership state from RHS. + INT_ASSERT(gen->get(lindex) == false); + if (!gen->get(rindex)) + { + if (fWarnOwnership) + USR_WARN(rsym, "Uninitialized symbol is copied here"); + // This can be a problem because the way we track aliases assumes that the + // ownership state of every member of the alias clique is the same. + // If this assumption turns out to be untrue, then we have to track aliases + // statement by statement, which will be more expensive. + // This assumption would be violated if for example we have: + // var a + // var b + // (move b a) + // (move a _defaultOf(type_tmp)) + // Now b is an alias of a (they way we currently track aliases), but the + // value of a is owned while the value of b is not. + } + else + { + gen->set(lindex); + } +} + + +static void createAlias(SymExpr* se, AliasVectorMap& aliases) +{ + CallExpr* call = toCallExpr(se->parentExpr); + + SymExpr* lhs = toSymExpr(call->get(1)); + SymExpr* rhs = toSymExpr(call->get(2)); + + Symbol* lsym = lhs->var; + Symbol* rsym = rhs->var; + + // Merge aliases whether or not they are live. + aliases.merge(rsym, lsym); } // If this call acts like a destructor, then add the symbols it affects to the -// kill set. -static void processDestructor(CallExpr* call, SymExpr* se, BitVec* kill, - AliasVectorMap& aliases, - const SymbolIndexMap& symbolIndex) +// kill set and remove them from the gen set. +static void processConsumer(SymExpr* se, BitVec* gen, BitVec* kill, + const AliasVectorMap& aliases, + const SymbolIndexMap& symbolIndex) { - if (FnSymbol* fn = call->isResolved()) - { - // This is a function call. + // Add all members of an alias clique to the kill set and remove them from + // the gen set. - // The only one we're interested in right now is a destructor call. - if (fn->hasFlag(FLAG_DESTRUCTOR)) - { - // Paranoid check: This SymExpr is the thing being destroyed, right? - INT_ASSERT(call->get(1) == se); + // All members of an alias clique point to the same SymbolVector, so we only + // need to look up one arbitrarily and then run the list. + Symbol* sym = se->var; + SymbolVector* aliasList = aliases.at(sym); - processDestructor(se, kill, aliases, symbolIndex); - } - } - else + for_vector(Symbol, alias, *aliasList) { - // This is a primitive. - if (call->isPrimitive(PRIM_RETURN)) - { - // Returns act like destructors. - processDestructor(se, kill, aliases, symbolIndex); - } - } + size_t index = symbolIndex.at(alias); + gen->reset(index); + kill->set(index); + } } @@ -467,13 +613,17 @@ static void computeTransitions(SymExprVector& symExprs, if (symbolIndex.find(sym) == symbolIndex.end()) continue; - // We are only interested in call expressions involving the SymExpr. - if (CallExpr* call = toCallExpr(se->parentExpr)) + if (isCreated(se)) + processCreator(se, gen, symbolIndex); + + if (isBitwiseCopy(se)) { - processConstructor(call, se, gen, symbolIndex); - processMove(call, se, gen, aliases, symbolIndex); - processDestructor(call, se, kill, aliases, symbolIndex); + processBitwiseCopy(se, gen, symbolIndex); + createAlias(se, aliases); } + + if (isConsumed(se)) + processConsumer(se, gen, kill, aliases, symbolIndex); } } @@ -514,47 +664,117 @@ static void computeTransitions(FnSymbol* fn, } -// In backward flow, we adjust the out set so it is the intersection of the IN -// sets of its successors. If the block is a terminal block (a block with no -// successors), however, we set its OUT to all zeroes. This forces ownership to -// be driven to zero before the function is exited. -// This analysis will work correctly even if the function has multiple exits. -static void backwardFlowOwnership(const BasicBlock::BasicBlockVector& bbs, - FlowSet& IN, FlowSet& OUT) +static bool isRetVarInReturn(SymExpr* se) { - size_t nbbs = bbs.size(); - for (size_t i = 0; i < nbbs; ++i) + if (CallExpr* call = toCallExpr(se->parentExpr)) + if (call->isPrimitive(PRIM_RETURN)) + // We just assume that that call->get(1) == se. + // What else could it be? + return true; + + return false; +} + + +// Insert an autoCopy because this symbol is unowned and +// a receptor formal expects is operand to be owned. +static void insertAutoCopy(SymExpr* se) +{ + SET_LINENO(se); + Symbol* sym = se->var; + FnSymbol* autoCopyFn = autoCopyMap.get(sym->type); + + if (autoCopyFn == NULL) + return; + + // For now, we ignore internally reference-counted types. We'll add the + // code to manage those later. + if (isRefCountedType(sym->type)) + return; + + // Prevent autoCopy functions from calling themselves recursively. + // TODO: Remove this clause after the autoCopy function becomes a copy constructor + // method. + if (isRetVarInReturn(se)) + return; + + CallExpr* autoCopyCall = new CallExpr(autoCopyFn, se->copy()); + se->replace(autoCopyCall); +} + + +static void insertAutoCopy(SymExprVector& symExprs, BitVec* gen, BitVec* kill, + const AliasVectorMap& aliases, + const SymbolIndexMap& symbolIndex) +{ + for_vector(SymExpr, se, symExprs) { - if (bbs[i]->outs.size() == 0) - { - // This is a terminal block because it has no successors. - // Its OUT set must be empty. - OUT[i]->clear(); - } - else + // We are only interested in local symbols, so if this one does not appear + // in our map, move on. + Symbol* sym = se->var; + if (symbolIndex.find(sym) == symbolIndex.end()) + continue; + + // Set a bit in the gen set if this is a constructor. + if (isCreated(se)) + processCreator(se, gen, symbolIndex); + + // Pass ownership to this symbol if it is the result of a bitwise copy. + if (isBitwiseCopy(se)) + processBitwiseCopy(se, gen, symbolIndex); + + if (isConsumed(se)) { - BitVec* out = OUT[i]; - out->set(); - for_vector(BasicBlock, succ, bbs[i]->outs) - *out &= *IN[succ->id]; + // If the gen bit is set for this symbol, we can leave it as a move and + // transfer ownership. Otherwise, we need to insert an autoCopy. + size_t index = symbolIndex.at(sym); + if (!gen->get(index)) + insertAutoCopy(se); + + processConsumer(se, gen, kill, aliases, symbolIndex); } } } -// This predicate determines if the given statement is a jump. -static bool isJump(Expr* stmt) +static void insertAutoCopy(BasicBlock& bb, + BitVec* gen, BitVec* kill, + const SymbolVector& symbols, + const SymbolIndexMap& symbolIndex, + const AliasVectorMap& aliases) { - // A goto is definitely a jump. - if (isGotoStmt(stmt)) - return true; + for_vector(Expr, expr, bb.exprs) + { + SymExprVector symExprs; + collectSymExprsSTL(expr, symExprs); - // A return primitive works like a jump. (Nothing should appear after it.) - if (CallExpr* call = toCallExpr(stmt)) - if (call->isPrimitive(PRIM_RETURN)) - return true; + insertAutoCopy(symExprs, gen, kill, aliases, symbolIndex); + } +} - return false; + +static void insertAutoCopy(FnSymbol* fn, + FlowSet& GEN, FlowSet& KILL, + FlowSet& IN, FlowSet& OUT, + const SymbolVector& symbols, + const SymbolIndexMap& symbolIndex, + const AliasVectorMap& aliases) +{ + size_t nbbs = fn->basicBlocks->size(); + for (size_t i = 0; i < nbbs; i++) + { + // We need to insert an autodestroy call for each symbol that is owned + // (live) at the end of the block but is unowned (dead) in the OUT set. + BasicBlock& bb = *(*fn->basicBlocks)[i]; + // For this block traversal, we initialize GEN[i] = IN[i]. + // We don't care about the local kill, so we just use a new one. + // (We could compare new with old as a sanity check following the traversal.) + BitVec* local_gen = new BitVec(*IN[i]); + BitVec* local_kill = new BitVec(KILL[i]->size()); + insertAutoCopy(bb, local_gen, local_kill, symbols, symbolIndex, aliases); + delete local_kill; local_kill = 0; + delete local_gen; local_gen = 0; + } } @@ -635,8 +855,36 @@ static void insertAutoDestroy(FnSymbol* fn, } +// In backward flow, we adjust the out set so it is the intersection of the IN +// sets of its successors. If the block is a terminal block (a block with no +// successors), however, we set its OUT to all zeroes. This forces ownership to +// be driven to zero before the function is exited. +// This analysis will work correctly even if the function has multiple exits. +static void backwardFlowOwnership(const BasicBlock::BasicBlockVector& bbs, + FlowSet& IN, FlowSet& OUT) +{ + size_t nbbs = bbs.size(); + for (size_t i = 0; i < nbbs; ++i) + { + if (bbs[i]->outs.size() == 0) + { + // This is a terminal block because it has no successors. + // Its OUT set must be empty. + OUT[i]->clear(); + } + else + { + BitVec* out = OUT[i]; + out->set(); + for_vector(BasicBlock, succ, bbs[i]->outs) + *out &= *IN[succ->id]; + } + } +} + + // Insert autoDestroy calls in a function as needed. -static void insertAutoDestroy(FnSymbol* fn) +static void insertAutoCopyAutoDestroy(FnSymbol* fn) { BasicBlock::buildBasicBlocks(fn); size_t nbbs = fn->basicBlocks->size(); @@ -674,6 +922,8 @@ static void insertAutoDestroy(FnSymbol* fn) // flows through the basic blocks. BasicBlock::forwardFlowAnalysis(fn, GEN, KILL, IN, OUT, true); + insertAutoCopy(fn, GEN, KILL, IN, OUT, symbols, symbolIndex, aliases); + // We need our own equation for backward flow. // Backward flow determines where ownership must be given up through a // delete, by making the OUT set the AND of all its successor INs and the IN @@ -684,8 +934,6 @@ static void insertAutoDestroy(FnSymbol* fn) insertAutoDestroy(fn, GEN, KILL, IN, OUT, symbols, symbolIndex, aliases); - destroyAliasVectorMap(aliases); - destroyFlowSet(GEN); destroyFlowSet(KILL); destroyFlowSet(IN); @@ -693,7 +941,7 @@ static void insertAutoDestroy(FnSymbol* fn) } -static void insertAutoDestroy() +void insertAutoCopyAutoDestroy() { forv_Vec(FnSymbol, fn, gFnSymbols) { @@ -701,16 +949,6 @@ static void insertAutoDestroy() if (fn->hasFlag(FLAG_FUNCTION_PROTOTYPE)) continue; - insertAutoDestroy(fn); + insertAutoCopyAutoDestroy(fn); } } - - -void insertAutoCopyAutoDestroy() -{ - // First of all, just insert autoDestroy calls. - // We assume that all necessary autoCopy calls have been added. - // As an enhancement/optimization, we'll add a separate pass to compute and - // add the minimum number of autoCopy calls required. - insertAutoDestroy(); -} From 739d2a7c5aa4b9c5f2c85e1604b47d1b30b67513 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Tue, 6 Jan 2015 01:39:53 -0600 Subject: [PATCH 18/53] Update return-record.chpl with initCopy function. --- test/types/records/kbrady/return-record.chpl | 10 ++++++- test/types/records/kbrady/return-record.good | 30 +++++++++++++++++--- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/test/types/records/kbrady/return-record.chpl b/test/types/records/kbrady/return-record.chpl index ddc0e04f35eb..bb3c8421d35b 100644 --- a/test/types/records/kbrady/return-record.chpl +++ b/test/types/records/kbrady/return-record.chpl @@ -14,10 +14,18 @@ module foo { proc ~Zed() { writeln("in ~Zed(",x,")"); - delete this.x; + delete this.x; this.x = nil; } } + pragma "init copy fn" + proc chpl__initCopy(const ref rhs: Zed) { + writeln("in chpl__initCopy(", rhs.x, ":Zed)"); + var result: Zed; + result.x = if rhs.x then new helper(rhs.x.y) else nil; + return result; + } + proc =(ref lhs: Zed, rhs: Zed) { if debugAssign then writeln("in =(Zed(",lhs.x,"), Zed(",rhs.x,"))"); else writeln("in =(Zed, Zed)"); diff --git a/test/types/records/kbrady/return-record.good b/test/types/records/kbrady/return-record.good index 0bcf49f1a19b..a8a925ce49a2 100644 --- a/test/types/records/kbrady/return-record.good +++ b/test/types/records/kbrady/return-record.good @@ -2,10 +2,20 @@ type inferred ------------- in baz() in Zed(1) -in =(Zed, Zed) +in chpl__initCopy({y = 1}:Zed) +in chpl__initCopy({y = 1}:Zed) +in chpl__initCopy({y = 1}:Zed) +in chpl__initCopy({y = 1}:Zed) +in chpl__initCopy({y = 1}:Zed) +in chpl__initCopy({y = 1}:Zed) +(x = {y = 1})in ~Zed({y = 1}) + +in ~Zed({y = 1}) +in ~Zed({y = 1}) +in ~Zed({y = 1}) +in ~Zed({y = 1}) +in ~Zed({y = 1}) in ~Zed({y = 1}) -(x = {y = 2}) -in ~Zed({y = 2}) type specified -------------- @@ -13,5 +23,17 @@ in gob() in Zed(1) in =(Zed, Zed) in ~Zed({y = 1}) -(x = {y = 2}) +in chpl__initCopy({y = 2}:Zed) +in chpl__initCopy({y = 2}:Zed) +in chpl__initCopy({y = 2}:Zed) +in chpl__initCopy({y = 2}:Zed) +in chpl__initCopy({y = 2}:Zed) +in chpl__initCopy({y = 2}:Zed) +(x = {y = 2})in ~Zed({y = 2}) + +in ~Zed({y = 2}) +in ~Zed({y = 2}) +in ~Zed({y = 2}) +in ~Zed({y = 2}) +in ~Zed({y = 2}) in ~Zed({y = 2}) From 627d5c73f20b0440dd74cb94a57d10eb46139097 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Tue, 6 Jan 2015 01:46:46 -0600 Subject: [PATCH 19/53] Added a few more cases to isConsumed(), so that more autoCopy calls are inserted. In isConsumed(), added cases for PRIM_MOVE to a global and PRIM_SET_MEMBER. Use temporaries to store the result of an autocopy call, to keep returnStarTuplesByRefArgs happy. 1361 failures. W00t! --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 44 ++++++++++++------- compiler/passes/returnStarTuplesByRefArgs.cpp | 3 ++ 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index 397a1484d592..a43e9f1f9dcd 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -451,26 +451,35 @@ static bool isConsumed(SymExpr* se) else { // This is a primitive. - if (call->isPrimitive(PRIM_RETURN)) + switch(call->primitive->tag) { + default: + // Assume that, generally speaking, a primitive does not consume its argument. + return false; + + case PRIM_RETURN: // Returns act like destructors. return true; - } -#if 0 - // Not yet. - if (call->isPrimitive(PRIM_MOVE)) - { - // If the left side of a move is a global, it assumes ownership from the - // RHS. We can assume that the RHS is local. - Expr* lhs = call->get(1); - if (SymExpr* lhse = toSymExpr(lhs)) + + case PRIM_MOVE: { - if (lhse->var->type->symbol->hasFlag(FLAG_REF) || - lhse->var->defPoint->parentSymbol->hasFlag(FLAG_MODULE_INIT)) - return true; + // If the left side of a move is a global, it assumes ownership from the + // RHS. We can assume that the RHS is local. + Expr* lhs = call->get(1); + if (SymExpr* lhse = toSymExpr(lhs)) + { + if (//lhse->var->type->symbol->hasFlag(FLAG_REF) || + isModuleSymbol(lhse->var->defPoint->parentSymbol)) + return true; + } } + break; + + case PRIM_SET_MEMBER: + if (call->get(3) == se) + return true; + break; } -#endif } } @@ -698,8 +707,13 @@ static void insertAutoCopy(SymExpr* se) if (isRetVarInReturn(se)) return; + Expr* stmt = se->getStmtExpr(); + VarSymbol* tmp = newTemp("auto_copy_tmp", sym->type); CallExpr* autoCopyCall = new CallExpr(autoCopyFn, se->copy()); - se->replace(autoCopyCall); + stmt->insertBefore(new DefExpr(tmp)); + stmt->insertBefore(new CallExpr(PRIM_MOVE, tmp, autoCopyCall)); + se->replace(new SymExpr(tmp)); + insertReferenceTemps(autoCopyCall); } diff --git a/compiler/passes/returnStarTuplesByRefArgs.cpp b/compiler/passes/returnStarTuplesByRefArgs.cpp index aa9758d6837c..ad98361bd3b3 100644 --- a/compiler/passes/returnStarTuplesByRefArgs.cpp +++ b/compiler/passes/returnStarTuplesByRefArgs.cpp @@ -85,6 +85,9 @@ void returnStarTuplesByRefArgs() { // SET/GET_SVEC_MEMBER primitives // forv_Vec(CallExpr, call, gCallExprs) { + if (! call->inTree()) + continue; + if (call->isPrimitive(PRIM_SET_MEMBER) || call->isPrimitive(PRIM_GET_MEMBER) || call->isPrimitive(PRIM_GET_MEMBER_VALUE)) { From 0b21d043a70d6a4509447d31a96212bf4f8b0f0b Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Tue, 6 Jan 2015 14:05:49 -0600 Subject: [PATCH 20/53] Comment out the clause that prevents insertion of autoCopy calls for reference counted types. Some tests involving internally ref-counted types were failing due to the reference count going negative. This turned out to be due to record-wrapped types (the same set) being inserted into tuples without their copy-constructor (autoCopy) being called. The destructor for a tuple calls autoDestroy on its elements, so these autoCopy calls are required. It is probably the case that destructor calls were omitted for record-wrapped elements in a tuple. But since I'm coding the insertion of destructor calls from scratch and trying to keep the implementation as uniform as possible, tuple destructors contain destructors for all of their record elements, and are called wherever a tuple local needs to be destroyed. Adding more autoCopies exposed that we may have RWT objects that are empty (i.e. contain a nil class object) and autoCopy should be robust to that. 291 failures remaining --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 5 +++++ modules/internal/ChapelBase.chpl | 12 +++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index a43e9f1f9dcd..24ba34b3bae3 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -696,10 +696,15 @@ static void insertAutoCopy(SymExpr* se) if (autoCopyFn == NULL) return; +// This clause is commented out, because when arrays were inserted into tuples, +// their refcounts were not getting bumped, but when the tuple is deleted, the +// array is deleted and that decrements the reference count. +#if 0 // For now, we ignore internally reference-counted types. We'll add the // code to manage those later. if (isRefCountedType(sym->type)) return; +#endif // Prevent autoCopy functions from calling themselves recursively. // TODO: Remove this clause after the autoCopy function becomes a copy constructor diff --git a/modules/internal/ChapelBase.chpl b/modules/internal/ChapelBase.chpl index e10ad72fc331..99eb4d1ab602 100644 --- a/modules/internal/ChapelBase.chpl +++ b/modules/internal/ChapelBase.chpl @@ -1011,7 +1011,9 @@ module ChapelBase { pragma "removable auto copy" pragma "donor fn" pragma "auto copy fn" proc chpl__autoCopy(x: _distribution) { - if !noRefCount then x._value.incRefCount(); + if !noRefCount then + if x._value != nil then + x._value.incRefCount(); return x; } @@ -1019,7 +1021,9 @@ module ChapelBase { pragma "removable auto copy" pragma "donor fn" pragma "auto copy fn" proc chpl__autoCopy(x: domain) { - if !noRefCount then x._value.incRefCount(); + if !noRefCount then + if x._value != nil then + x._value.incRefCount(); return x; } @@ -1027,7 +1031,9 @@ module ChapelBase { pragma "removable auto copy" pragma "donor fn" pragma "auto copy fn" proc chpl__autoCopy(x: []) { - if !noRefCount then x._value.incRefCount(); + if !noRefCount then + if x._value != nil then + x._value.incRefCount(); return x; } From 7096e7136067ab4b4c51b5cac089852a460c4dc1 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Tue, 6 Jan 2015 18:28:49 -0600 Subject: [PATCH 21/53] Strengthened the test in isBitwiseCopy(), so it returns false if the RHS is global. Removed the commented-out clause that prevented autoCopy insertion for ref-counted types (no change in functionality). Updated some test output because the copy constructor is called for members of tuples much more often now. In users/ferguson/refcnt, the output changed because autodestroy calls have been moved closer to the end of the routine. This is good, because it shows that in the new implementation there are more opportunities for an owned variable to be consumed before it goes out of scope. --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 38 ++++++++----------- test/classes/diten/record_initialize.good | 25 +++++++++++- .../records/hilde/newUserDefaultCtor.good | 2 +- test/users/ferguson/refcnt.good | 22 +++++------ 4 files changed, 51 insertions(+), 36 deletions(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index 24ba34b3bae3..7b568857e52c 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -408,7 +408,8 @@ static bool isCreated(SymExpr* se) // Returns true if the expression in which the given SymExpr appears makes it a // bitwise copy of some other symbol; false otherwise. -static bool isBitwiseCopy(SymExpr* se) +static bool isBitwiseCopy(SymExpr* se, + const SymbolIndexMap& symbolIndex) { // Must be a call (as opposed to a DefExpr or LabelExpr, etc. if (CallExpr* call = toCallExpr(se->parentExpr)) @@ -421,8 +422,17 @@ static bool isBitwiseCopy(SymExpr* se) if (call->get(1) == se) { // The second argument must also be a symexpr. - if (isSymExpr(call->get(2))) + if (SymExpr* rhse = toSymExpr(call->get(2))) + { + // And it must contain a local symbol. + Symbol* rhs = rhse->var; + if (symbolIndex.find(rhs) == symbolIndex.end()) + // Not found, so skip this move. + // The lhs will be considered unowned, which is correct because it + // is just a bitwise copy of the value of the global. + return false; return true; + } } } } @@ -536,15 +546,7 @@ static void processBitwiseCopy(SymExpr* se, BitVec* gen, Symbol* rsym = rhs->var; size_t lindex = symbolIndex.at(lsym); - SymbolIndexMap::const_iterator rsymPair = symbolIndex.find(rsym); - if (rsymPair == symbolIndex.end()) - { - // TODO: A move from a global or extern symbol without a copy-constructor - // call is probably an AST consistency or codegen error, given that - // we have already weeded out fundamental, extern and class types. - return; - } - size_t rindex = rsymPair->second; + size_t rindex = symbolIndex.at(rsym); // Copy ownership state from RHS. INT_ASSERT(gen->get(lindex) == false); @@ -625,7 +627,7 @@ static void computeTransitions(SymExprVector& symExprs, if (isCreated(se)) processCreator(se, gen, symbolIndex); - if (isBitwiseCopy(se)) + if (isBitwiseCopy(se, symbolIndex)) { processBitwiseCopy(se, gen, symbolIndex); createAlias(se, aliases); @@ -696,16 +698,6 @@ static void insertAutoCopy(SymExpr* se) if (autoCopyFn == NULL) return; -// This clause is commented out, because when arrays were inserted into tuples, -// their refcounts were not getting bumped, but when the tuple is deleted, the -// array is deleted and that decrements the reference count. -#if 0 - // For now, we ignore internally reference-counted types. We'll add the - // code to manage those later. - if (isRefCountedType(sym->type)) - return; -#endif - // Prevent autoCopy functions from calling themselves recursively. // TODO: Remove this clause after the autoCopy function becomes a copy constructor // method. @@ -739,7 +731,7 @@ static void insertAutoCopy(SymExprVector& symExprs, BitVec* gen, BitVec* kill, processCreator(se, gen, symbolIndex); // Pass ownership to this symbol if it is the result of a bitwise copy. - if (isBitwiseCopy(se)) + if (isBitwiseCopy(se, symbolIndex)) processBitwiseCopy(se, gen, symbolIndex); if (isConsumed(se)) diff --git a/test/classes/diten/record_initialize.good b/test/classes/diten/record_initialize.good index e2ce865036f1..77897d4ced0a 100644 --- a/test/classes/diten/record_initialize.good +++ b/test/classes/diten/record_initialize.good @@ -1,3 +1,26 @@ BBB.initialize() +BBB.initialize() +BBB.initialize() +AAA.initialize() +BBB.initialize() +BBB.initialize() +BBB.initialize() +AAA.initialize() +BBB.initialize() +BBB.initialize() +BBB.initialize() +AAA.initialize() +BBB.initialize() +BBB.initialize() +BBB.initialize() +AAA.initialize() +BBB.initialize() +BBB.initialize() +BBB.initialize() +AAA.initialize() +BBB.initialize() +BBB.initialize() +BBB.initialize() AAA.initialize() -(b = (a = 0, b = 0, c = 0)) +(b = BBB.initialize() +(a = 0, b = 0, c = 0)) diff --git a/test/types/records/hilde/newUserDefaultCtor.good b/test/types/records/hilde/newUserDefaultCtor.good index ab72fd6b1ea8..f05b28394051 100644 --- a/test/types/records/hilde/newUserDefaultCtor.good +++ b/test/types/records/hilde/newUserDefaultCtor.good @@ -1,5 +1,5 @@ Called R(). Called R(). -Called ~R(). Done. Called ~R(). +Called ~R(). diff --git a/test/users/ferguson/refcnt.good b/test/users/ferguson/refcnt.good index 2ddd6727f38d..e0a04a721dfe 100644 --- a/test/users/ferguson/refcnt.good +++ b/test/users/ferguson/refcnt.good @@ -7,8 +7,6 @@ Starting test_one {count = 1} In R.routine {count = 1} In test_two {count = 1} Done test_one {count = 1} -In ~R() 1 -Destroying a RefCount After scope Before scope (copy initialization) Initializing a RefCount @@ -20,9 +18,6 @@ Starting test_one {count = 2} In R.routine {count = 2} In test_two {count = 2} Done test_one {count = 2} -In ~R() 2 -In ~R() 1 -Destroying a RefCount After scope (copy initialization) Before scope (copy initialization 2) Initializing a RefCount @@ -31,9 +26,6 @@ Starting test_one {count = 2} In R.routine {count = 2} In test_two {count = 2} Done test_one {count = 2} -In ~R() 2 -In ~R() 1 -Destroying a RefCount After scope (copy initialization 2) Before scope (setting global) Initializing a RefCount @@ -44,7 +36,6 @@ Starting R assign {count = 1}{count = 1} Destroying a RefCount Done R assign {count = 2} Done test_one {count = 2} -In ~R() 2 After scope (setting global) Before scope (begin) Initializing a RefCount @@ -54,9 +45,18 @@ In R.routine {count = 2} In test_two {count = 2} Done test_one {count = 2} In ~R() 2 -In ~R() 1 -Destroying a RefCount After scope (begin) Finishing In ~R() 1 Destroying a RefCount +In ~R() 2 +In ~R() 1 +Destroying a RefCount +In ~R() 2 +In ~R() 1 +Destroying a RefCount +In ~R() 2 +In ~R() 1 +Destroying a RefCount +In ~R() 1 +Destroying a RefCount From b5ad55a7b98e120b4df777fb5b08182f63f41017 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Sat, 13 Dec 2014 09:46:22 -0600 Subject: [PATCH 22/53] Remove returnRecordsByRefArguments and all autodestroy calls. This results in 211 regressions. --- compiler/AST/build.cpp | 8 ++++++++ compiler/passes/normalize.cpp | 7 +++++++ compiler/passes/scopeResolve.cpp | 3 ++- compiler/resolution/callDestructors.cpp | 17 ++++++++++++----- compiler/resolution/cullOverReferences.cpp | 5 +++++ compiler/resolution/functionResolution.cpp | 13 ++++++++++++- compiler/resolution/implementForallIntents.cpp | 2 ++ compiler/resolution/wrappers.cpp | 2 ++ 8 files changed, 50 insertions(+), 7 deletions(-) diff --git a/compiler/AST/build.cpp b/compiler/AST/build.cpp index b0ec0cc02c59..e82b449284ac 100644 --- a/compiler/AST/build.cpp +++ b/compiler/AST/build.cpp @@ -585,13 +585,17 @@ destructureIndices(BlockStmt* block, var->addFlag(FLAG_INDEX_VAR); if (coforall) var->addFlag(FLAG_COFORALL_INDEX_VAR); +#ifndef HILDE_MM var->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif } else if (SymExpr* sym = toSymExpr(indices)) { block->insertAtHead(new CallExpr(PRIM_MOVE, sym->var, init)); sym->var->addFlag(FLAG_INDEX_VAR); if (coforall) sym->var->addFlag(FLAG_COFORALL_INDEX_VAR); +#ifndef HILDE_MM sym->var->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif } } @@ -1058,7 +1062,9 @@ buildForallLoopStmt(Expr* indices, followIdx->addFlag(FLAG_INDEX_OF_INTEREST); leadIdxCopy->addFlag(FLAG_INDEX_VAR); +#ifndef HILDE_MM leadIdxCopy->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif followIdx->addFlag(FLAG_INDEX_OF_INTEREST); // ForallLeaderArgs: stash references so we know where things are. @@ -1456,7 +1462,9 @@ CallExpr* buildReduceExpr(Expr* opExpr, Expr* dataExpr, bool zippered) { VarSymbol* localOp = newTemp(); leadIdxCopy->addFlag(FLAG_INDEX_VAR); +#ifndef HILDE_MM leadIdxCopy->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif ForLoop* followBody = new ForLoop(followIdx, followIter, NULL); diff --git a/compiler/passes/normalize.cpp b/compiler/passes/normalize.cpp index b052417eb329..f7447c413945 100644 --- a/compiler/passes/normalize.cpp +++ b/compiler/passes/normalize.cpp @@ -488,7 +488,9 @@ static void insertRetMove(FnSymbol* fn, VarSymbol* retval, CallExpr* ret) { // copy to the return value variable. // Contrast this with a move, which merely shares ownership between the // bitwise copies of the object. +#ifndef HILDE_MM retval->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif } else if (!fn->hasFlag(FLAG_WRAPPER) && strcmp(fn->name, "iteratorIndex") && strcmp(fn->name, "iteratorIndexHelp")) @@ -829,8 +831,11 @@ static void insert_call_temps(CallExpr* call) VarSymbol* tmp = newTemp("call_tmp"); if (!parentCall || !parentCall->isNamed("chpl__initCopy")) tmp->addFlag(FLAG_EXPR_TEMP); +#ifndef HILDE_MM + // Can't we figure this out later? if (call->isPrimitive(PRIM_NEW)) tmp->addFlag(FLAG_INSERT_AUTO_DESTROY_FOR_EXPLICIT_NEW); +#endif if (call->isPrimitive(PRIM_TYPEOF)) tmp->addFlag(FLAG_TYPE_VARIABLE); tmp->addFlag(FLAG_MAYBE_PARAM); @@ -857,6 +862,7 @@ fix_def_expr(VarSymbol* var) { if (!type && !init) return; // already fixed +#ifndef HILDE_MM // // add "insert auto destroy" pragma to user variables that should be // auto destroyed @@ -873,6 +879,7 @@ fix_def_expr(VarSymbol* var) { fn->_this != var && // Note 2. !fn->hasFlag(FLAG_TYPE_CONSTRUCTOR)) var->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif // // handle "no copy" variables diff --git a/compiler/passes/scopeResolve.cpp b/compiler/passes/scopeResolve.cpp index f4a455dbe420..1a166ff1581e 100644 --- a/compiler/passes/scopeResolve.cpp +++ b/compiler/passes/scopeResolve.cpp @@ -887,8 +887,9 @@ static void build_constructor(AggregateType* ct) { if (!field->hasFlag(FLAG_TYPE_VARIABLE) && !exprType) { // init && !exprType VarSymbol* tmp = newTemp(); - +#ifndef HILDE_MM tmp->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif tmp->addFlag(FLAG_MAYBE_PARAM); tmp->addFlag(FLAG_MAYBE_TYPE); diff --git a/compiler/resolution/callDestructors.cpp b/compiler/resolution/callDestructors.cpp index aaef004eff6c..8727309064c0 100644 --- a/compiler/resolution/callDestructors.cpp +++ b/compiler/resolution/callDestructors.cpp @@ -17,6 +17,8 @@ * limitations under the License. */ +#define HILDE_MM 1 + #include "passes.h" #include "astutil.h" @@ -28,6 +30,7 @@ #include "symbol.h" +#ifndef HILDE_MM // Clear autoDestroy flags on variables that get assigned to the return value of // certain functions. // @@ -663,7 +666,7 @@ returnRecordsByReferenceArguments() { } freeDefUseMaps(defMap, useMap); } - +#endif static void fixupDestructors() { @@ -758,7 +761,7 @@ fixupDestructors() { } } - +#ifndef HILDE_MM static void insertGlobalAutoDestroyCalls() { // --ipe does not build chpl_gen_main if (chpl_gen_main == NULL) @@ -783,7 +786,7 @@ static void insertGlobalAutoDestroyCalls() { } fn->insertAtTail(new CallExpr(PRIM_RETURN, gVoid)); } - +#endif static void insertDestructorCalls() { @@ -800,7 +803,7 @@ static void insertDestructorCalls() } } - +#ifndef HILDE_MM static void insertAutoCopyTemps() { Map*> defMap; @@ -831,7 +834,7 @@ static void insertAutoCopyTemps() freeDefUseMaps(defMap, useMap); } - +#endif // This routine inserts autoCopy calls ahead of yield statements as necessary, // so the calling routine "owns" the returned value. @@ -913,12 +916,16 @@ void callDestructors() { fixupDestructors(); insertDestructorCalls(); +#ifndef HILDE_MM insertAutoCopyTemps(); cullAutoDestroyFlags(); cullExplicitAutoDestroyFlags(); insertAutoDestroyCalls(); returnRecordsByReferenceArguments(); +#endif insertYieldTemps(); +#ifndef HILDE_MM insertGlobalAutoDestroyCalls(); +#endif insertReferenceTemps(); } diff --git a/compiler/resolution/cullOverReferences.cpp b/compiler/resolution/cullOverReferences.cpp index 3d43a2eff06d..4269f536afb9 100644 --- a/compiler/resolution/cullOverReferences.cpp +++ b/compiler/resolution/cullOverReferences.cpp @@ -109,10 +109,15 @@ void cullOverReferences() { base->var = copy; VarSymbol* tmp = newTemp(copy->retType); move->insertBefore(new DefExpr(tmp)); +#ifndef HILDE_MM + // Why is this here? + // Marking variables for autocopy/autodestroy ought to be done in + // one place once and for all. Is this that place? if (requiresImplicitDestroy(call)) { tmp->addFlag(FLAG_INSERT_AUTO_COPY); tmp->addFlag(FLAG_INSERT_AUTO_DESTROY); } +#endif if (useMap.get(se->var) && useMap.get(se->var)->n > 0) { move->insertAfter(new CallExpr(PRIM_MOVE, se->var, new CallExpr(PRIM_ADDR_OF, tmp))); diff --git a/compiler/resolution/functionResolution.cpp b/compiler/resolution/functionResolution.cpp index 37850528b529..f2546d7b2fbd 100644 --- a/compiler/resolution/functionResolution.cpp +++ b/compiler/resolution/functionResolution.cpp @@ -233,7 +233,9 @@ Map iteratorFollowerMap; // iterator->leader map for promot //# Static Function Declarations //# static bool hasRefField(Type *type); +#ifndef HILDE_MM static bool typeHasRefField(Type *type); +#endif static FnSymbol* resolveUninsertedCall(Type* type, CallExpr* call); static void makeRefType(Type* type); static void resolveAutoCopy(Type* type); @@ -426,6 +428,7 @@ static bool hasRefField(Type *type) { return true; } +#ifndef HILDE_MM static bool typeHasRefField(Type *type) { if (AggregateType *ct = toAggregateType(type)) { for_fields(field, ct) { @@ -434,6 +437,7 @@ static bool typeHasRefField(Type *type) { } return false; } +#endif // // build reference type @@ -3781,7 +3785,9 @@ static void addLocalCopiesAndWritebacks(FnSymbol* fn, SymbolMap& formals2vars) !isRefCountedType(formalType)) { tmp->addFlag(FLAG_CONST); +#ifndef HILDE_MM tmp->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif } // This switch adds the extra code inside the current function necessary @@ -3825,7 +3831,9 @@ static void addLocalCopiesAndWritebacks(FnSymbol* fn, SymbolMap& formals2vars) // If the compiler verifies in a separate pass that it is never written, // we don't have to copy it. fn->insertAtHead(new CallExpr(PRIM_MOVE, tmp, new CallExpr("chpl__initCopy", formal))); +#ifndef HILDE_MM tmp->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif break; case INTENT_BLANK: @@ -3850,6 +3858,7 @@ static void addLocalCopiesAndWritebacks(FnSymbol* fn, SymbolMap& formals2vars) // intentional: It gives tuple-wrapped record-wrapped types different // behavior from bare record-wrapped types. fn->insertAtHead(new CallExpr(PRIM_MOVE, tmp, new CallExpr("chpl__autoCopy", formal))); +#ifndef HILDE_MM // WORKAROUND: // This is a temporary bug fix that results in leaked memory. // @@ -3878,6 +3887,7 @@ static void addLocalCopiesAndWritebacks(FnSymbol* fn, SymbolMap& formals2vars) (formal->type->getModule()->modTag==MOD_STANDARD))) || !typeHasRefField(formal->type)) tmp->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif } } else { @@ -5498,6 +5508,7 @@ postFold(Expr* expr) { } } if (!set) { +#ifndef HILDE_MM if (lhs->var->hasFlag(FLAG_EXPR_TEMP) && !lhs->var->hasFlag(FLAG_TYPE_VARIABLE)) { if (CallExpr* rhsCall = toCallExpr(call->get(2))) { @@ -5507,7 +5518,7 @@ postFold(Expr* expr) { } } } - +#endif if (isReferenceType(lhs->var->type) || lhs->var->type->symbol->hasFlag(FLAG_REF_ITERATOR_CLASS) || lhs->var->type->symbol->hasFlag(FLAG_ARRAY)) diff --git a/compiler/resolution/implementForallIntents.cpp b/compiler/resolution/implementForallIntents.cpp index eb8b0d6026a1..d1d28911c59d 100644 --- a/compiler/resolution/implementForallIntents.cpp +++ b/compiler/resolution/implementForallIntents.cpp @@ -286,7 +286,9 @@ static void detupleLeadIdx(Symbol* leadIdxSym, Symbol* leadIdxCopySym, for_vector(Symbol, svar, shadowVars) { lcCall->insertBefore(new DefExpr(svar)); extractFromLeaderYield(lcCall, ++ix, svar, leadIdxSym); +#ifndef HILDE_MM svar->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif } // Finally, remove the original assignment. diff --git a/compiler/resolution/wrappers.cpp b/compiler/resolution/wrappers.cpp index decd8e164489..60093099d8c0 100644 --- a/compiler/resolution/wrappers.cpp +++ b/compiler/resolution/wrappers.cpp @@ -225,9 +225,11 @@ buildDefaultWrapper(FnSymbol* fn, isRefCountedType(wrapper_formal->type)) { // Formal has a type expression attached and is reference counted (?). temp = newTemp("wrap_type_arg"); +#ifndef HILDE_MM if (Symbol* field = fn->_this->type->getField(formal->name, false)) if (field->defPoint->parentSymbol == fn->_this->type->symbol) temp->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif wrapper->insertAtTail(new DefExpr(temp)); // Give the formal its own copy of the type expression. From 3f634912abb935556546f04fc11cd947bd025ce4 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Sat, 13 Dec 2014 15:02:55 -0600 Subject: [PATCH 23/53] Make returnStarTuplesByRefArgs() put the return variable write-back in the right place. I stumbled across this long-standing bug after disabling returnRecordsByRefArguments(). Apparently that routine covered at least some cases that must be handled by returnStartTuplesByRefArgs() when it is disabled. If there is an early return in the routine, it will jump to the exit label. As written, returnStarTuplesByRefArgs() was putting the write-back to the return variable before the exit label. This works fine for the straight-line path, but fails to write out the result when the early return fires. This results in the caller using uninitialized memory, which is generally bad. The fix is a one-liner. The call to insertBeforeReturn() that inserts the write-back was changed to insertBeforeReturnAfterLabel(). That puts the write-back in the right place. --- compiler/passes/returnStarTuplesByRefArgs.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/passes/returnStarTuplesByRefArgs.cpp b/compiler/passes/returnStarTuplesByRefArgs.cpp index 60c5ea90f37a..9a30f0caac36 100644 --- a/compiler/passes/returnStarTuplesByRefArgs.cpp +++ b/compiler/passes/returnStarTuplesByRefArgs.cpp @@ -45,7 +45,7 @@ void returnStarTuplesByRefArgs() { ArgSymbol* arg = new ArgSymbol(INTENT_REF, "_ret", ret->type->refType); fn->insertFormalAtTail(arg); fn->retType = dtVoid; - fn->insertBeforeReturn(new CallExpr(PRIM_MOVE, arg, ret)); + fn->insertBeforeReturnAfterLabel(new CallExpr(PRIM_MOVE, arg, ret)); CallExpr* call = toCallExpr(fn->body->body.tail); INT_ASSERT(call && call->isPrimitive(PRIM_RETURN)); call->get(1)->replace(new SymExpr(gVoid)); From 10089bee02cba351c590d143a2878865b8c2ac5d Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Wed, 17 Dec 2014 18:09:31 -0600 Subject: [PATCH 24/53] Disable the removeUnnecessaryAutoCopyCalls pass. This still completes with 50 regressions, so has no impact on the output of the compiler. --- compiler/optimizations/removeUnnecessaryAutoCopyCalls.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/optimizations/removeUnnecessaryAutoCopyCalls.cpp b/compiler/optimizations/removeUnnecessaryAutoCopyCalls.cpp index e04b18deb1a0..f1370f18a384 100644 --- a/compiler/optimizations/removeUnnecessaryAutoCopyCalls.cpp +++ b/compiler/optimizations/removeUnnecessaryAutoCopyCalls.cpp @@ -439,7 +439,7 @@ static void removePODinitDestroy() void removeUnnecessaryAutoCopyCalls() { - if (fNoRemoveCopyCalls) + if (fNoRemoveCopyCalls || 1) // Disable this pass for a test. return; // From b17fb45363dd1e00b96758111a6b00363de353b3 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Mon, 22 Dec 2014 12:18:42 -0600 Subject: [PATCH 25/53] Add TODOs related to inserted chpl__autoCopy calls. It may be possible to remove some of these calls after the optimized insertion of autoDestroys is working, so this lets me track those places. The TODOs are flagged with TODO AMM for "Automatic Memory Management". diff --git a/compiler/AST/build.cpp b/compiler/AST/build.cpp index 87d86ee..53e9829 100644 --- a/compiler/AST/build.cpp +++ b/compiler/AST/build.cpp @@ -664,6 +664,7 @@ handleArrayTypeCase(FnSymbol* fn, Expr* indices, Expr* iteratorExpr, Expr* expr) thenStmt->insertAtTail(new DefExpr(domain)); // note that we need the below autoCopy until we start reference // counting domains within runtime array types + // TODO: Check if the explicit insertion of an autoCopy is necessary here. thenStmt->insertAtTail(new CallExpr(PRIM_MOVE, domain, new CallExpr("chpl__autoCopy", new CallExpr("chpl__ensureDomainExpr", diff --git a/compiler/passes/normalize.cpp b/compiler/passes/normalize.cpp index 0e9f207..ef8f12c 100644 --- a/compiler/passes/normalize.cpp +++ b/compiler/passes/normalize.cpp @@ -949,11 +949,15 @@ static void init_array_alias(VarSymbol* var, Expr* type, Expr* init, Expr* stmt) if (!type) { partial = new CallExpr("newAlias", gMethodToken, init->remove()); // newAlias is not a method, so we don't set the methodTag + // TODO: newAlias should return a constructed object, which would make + // this autoCopy redundant. stmt->insertAfter(new CallExpr(PRIM_MOVE, var, new CallExpr("chpl__autoCopy", partial))); } else { partial = new CallExpr("reindex", gMethodToken, init->remove()); partial->partialTag = true; partial->methodTag = true; + // TODO: Does reindex return a constructed object? If so, the autoCopy + // call is redundant. stmt->insertAfter(new CallExpr(PRIM_MOVE, var, new CallExpr("chpl__autoCopy", new CallExpr(partial, type->remove())))); } } diff --git a/compiler/resolution/functionResolution.cpp b/compiler/resolution/functionResolution.cpp index 6fb3507..316b150 100644 --- a/compiler/resolution/functionResolution.cpp +++ b/compiler/resolution/functionResolution.cpp @@ -2945,6 +2945,8 @@ static void setFlagsForConstAccess(CallExpr* call, FnSymbol* resolvedFn) // Report an error when storing a sync or single variable into a tuple. // This is because currently we deallocate memory excessively in this case. +// TODO: Review and see if this is still needed after comprehensive automatic +// memory management is in place. static void checkForStoringIntoTuple(CallExpr* call, FnSymbol* resolvedFn) { // Do not perform the checks if: @@ -3751,8 +3753,9 @@ insertFormalTemps(FnSymbol* fn) { // The copy in is needed for "inout", "in" and "const in" intents. // The copy out is needed for "inout" and "out" intents. // Blank intent is treated like "const", and normally copies the formal through -// chpl__autoCopy. +// chpl__autoCopy. (Check that this is still true.) // Note that autoCopy is called in this case, but not for "inout", "in" and "const in". +// TODO: Check that this special behavior is correct and necessary. // Either record-wrapped types are always passed by ref, or some unexpected // behavior will result by applying "in" intents to them. static void addLocalCopiesAndWritebacks(FnSymbol* fn, SymbolMap& formals2vars) @@ -3835,6 +3838,7 @@ static void addLocalCopiesAndWritebacks(FnSymbol* fn, SymbolMap& formals2vars) !ts->hasFlag(FLAG_ITERATOR_CLASS) && !ts->hasFlag(FLAG_ITERATOR_RECORD) && !getSyncFlags(ts).any()) { + // Todo, remove this special case and let parallel handle it. if (fn->hasFlag(FLAG_BEGIN)) { // autoCopy/autoDestroy will be added later, in parallel pass // by insertAutoCopyDestroyForTaskArg() @@ -5313,6 +5317,8 @@ insertValueTemp(Expr* insertPoint, Expr* actual) { // Currently, FLAG_DONOR_FN is only relevant when placed on // chpl__autoCopy(). // +// TODO: Would like to simplify this, so it returns true if the function +// returns a record or tuple and false otherwise. FnSymbol* requiresImplicitDestroy(CallExpr* call) { if (FnSymbol* fn = call->isResolved()) { @@ -7857,8 +7863,9 @@ static void replaceInitPrims(Vec& asts) VarSymbol* tmp = newTemp("_runtime_type_tmp_", runtimeTypeToValueFn->retType); call->getStmtExpr()->insertBefore(new DefExpr(tmp)); call->getStmtExpr()->insertBefore(new CallExpr(PRIM_MOVE, tmp, runtimeTypeToValueCall)); - INT_ASSERT(autoCopyMap.get(tmp->type)); - call->replace(new CallExpr(autoCopyMap.get(tmp->type), tmp)); + FnSymbol* autoCopy = autoCopyMap.get(tmp->type); + INT_ASSERT(autoCopy); + call->replace(new CallExpr(autoCopy, tmp)); } else if (rt->symbol->hasFlag(FLAG_HAS_RUNTIME_TYPE)) { // // This is probably related to a comment that used to handle diff --git a/compiler/resolution/wrappers.cpp b/compiler/resolution/wrappers.cpp index f54766e..2025c27 100644 --- a/compiler/resolution/wrappers.cpp +++ b/compiler/resolution/wrappers.cpp @@ -282,6 +282,9 @@ buildDefaultWrapper(FnSymbol* fn, { Symbol* copyTemp = newTemp("wrap_arg"); wrapper->insertAtTail(new DefExpr(copyTemp)); + // TODO: Insertion of this autocopy can probably be left to + // ownership flow analysis. Since the LHS is a structure member, + // the copy is required, but only if the RHS object is not owned. wrapper->insertAtTail(new CallExpr(PRIM_MOVE, copyTemp, new CallExpr("chpl__autoCopy", temp))); wrapper->insertAtTail( new CallExpr(PRIM_SET_MEMBER, wrapper->_this, --- compiler/AST/build.cpp | 1 + compiler/passes/normalize.cpp | 4 ++++ compiler/resolution/functionResolution.cpp | 13 ++++++++++--- compiler/resolution/wrappers.cpp | 3 +++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/compiler/AST/build.cpp b/compiler/AST/build.cpp index e82b449284ac..62dfe9137cb8 100644 --- a/compiler/AST/build.cpp +++ b/compiler/AST/build.cpp @@ -664,6 +664,7 @@ handleArrayTypeCase(FnSymbol* fn, Expr* indices, Expr* iteratorExpr, Expr* expr) thenStmt->insertAtTail(new DefExpr(domain)); // note that we need the below autoCopy until we start reference // counting domains within runtime array types + // TODO AMM: Check if the explicit insertion of an autoCopy is necessary here. thenStmt->insertAtTail(new CallExpr(PRIM_MOVE, domain, new CallExpr("chpl__autoCopy", new CallExpr("chpl__ensureDomainExpr", diff --git a/compiler/passes/normalize.cpp b/compiler/passes/normalize.cpp index f7447c413945..74b76f4d0254 100644 --- a/compiler/passes/normalize.cpp +++ b/compiler/passes/normalize.cpp @@ -949,11 +949,15 @@ static void init_array_alias(VarSymbol* var, Expr* type, Expr* init, Expr* stmt) if (!type) { partial = new CallExpr("newAlias", gMethodToken, init->remove()); // newAlias is not a method, so we don't set the methodTag + // TODO AMM: newAlias should return a constructed object, which would make + // this autoCopy redundant. stmt->insertAfter(new CallExpr(PRIM_MOVE, var, new CallExpr("chpl__autoCopy", partial))); } else { partial = new CallExpr("reindex", gMethodToken, init->remove()); partial->partialTag = true; partial->methodTag = true; + // TODO AMM: Does reindex return a constructed object? If so, the autoCopy + // call is redundant. stmt->insertAfter(new CallExpr(PRIM_MOVE, var, new CallExpr("chpl__autoCopy", new CallExpr(partial, type->remove())))); } } diff --git a/compiler/resolution/functionResolution.cpp b/compiler/resolution/functionResolution.cpp index f2546d7b2fbd..3bb5095d5338 100644 --- a/compiler/resolution/functionResolution.cpp +++ b/compiler/resolution/functionResolution.cpp @@ -2955,6 +2955,8 @@ static void setFlagsForConstAccess(CallExpr* call, FnSymbol* resolvedFn) // Report an error when storing a sync or single variable into a tuple. // This is because currently we deallocate memory excessively in this case. +// TODO AMM: Review and see if this is still needed after comprehensive automatic +// memory management is in place. static void checkForStoringIntoTuple(CallExpr* call, FnSymbol* resolvedFn) { // Do not perform the checks if: @@ -3761,8 +3763,9 @@ insertFormalTemps(FnSymbol* fn) { // The copy in is needed for "inout", "in" and "const in" intents. // The copy out is needed for "inout" and "out" intents. // Blank intent is treated like "const", and normally copies the formal through -// chpl__autoCopy. +// chpl__autoCopy. (Check that this is still true.) // Note that autoCopy is called in this case, but not for "inout", "in" and "const in". +// TODO AMM: Check that this special behavior is correct and necessary. // Either record-wrapped types are always passed by ref, or some unexpected // behavior will result by applying "in" intents to them. static void addLocalCopiesAndWritebacks(FnSymbol* fn, SymbolMap& formals2vars) @@ -3845,6 +3848,7 @@ static void addLocalCopiesAndWritebacks(FnSymbol* fn, SymbolMap& formals2vars) !ts->hasFlag(FLAG_ITERATOR_CLASS) && !ts->hasFlag(FLAG_ITERATOR_RECORD) && !getSyncFlags(ts).any()) { + // TODO AMM: Remove this special case and let parallel handle it. if (fn->hasFlag(FLAG_BEGIN)) { // autoCopy/autoDestroy will be added later, in parallel pass // by insertAutoCopyDestroyForTaskArg() @@ -5344,6 +5348,8 @@ insertValueTemp(Expr* insertPoint, Expr* actual) { // Currently, FLAG_DONOR_FN is only relevant when placed on // chpl__autoCopy(). // +// TODO AMM: Would like to simplify this, so it returns true if the function +// returns a record or tuple and false otherwise. FnSymbol* requiresImplicitDestroy(CallExpr* call) { if (FnSymbol* fn = call->isResolved()) { @@ -7916,8 +7922,9 @@ static void replaceInitPrims(Vec& asts) VarSymbol* tmp = newTemp("_runtime_type_tmp_", runtimeTypeToValueFn->retType); call->getStmtExpr()->insertBefore(new DefExpr(tmp)); call->getStmtExpr()->insertBefore(new CallExpr(PRIM_MOVE, tmp, runtimeTypeToValueCall)); - INT_ASSERT(autoCopyMap.get(tmp->type)); - call->replace(new CallExpr(autoCopyMap.get(tmp->type), tmp)); + FnSymbol* autoCopy = autoCopyMap.get(tmp->type); + INT_ASSERT(autoCopy); + call->replace(new CallExpr(autoCopy, tmp)); } else if (rt->symbol->hasFlag(FLAG_HAS_RUNTIME_TYPE)) { // // This is probably related to a comment that used to handle diff --git a/compiler/resolution/wrappers.cpp b/compiler/resolution/wrappers.cpp index 60093099d8c0..5662d8f3984e 100644 --- a/compiler/resolution/wrappers.cpp +++ b/compiler/resolution/wrappers.cpp @@ -282,6 +282,9 @@ buildDefaultWrapper(FnSymbol* fn, { Symbol* copyTemp = newTemp("wrap_arg"); wrapper->insertAtTail(new DefExpr(copyTemp)); + // TODO AMM: Insertion of this autocopy can probably be left to + // ownership flow analysis. Since the LHS is a structure member, + // the copy is required, but only if the RHS object is not owned. wrapper->insertAtTail(new CallExpr(PRIM_MOVE, copyTemp, new CallExpr("chpl__autoCopy", temp))); wrapper->insertAtTail( new CallExpr(PRIM_SET_MEMBER, wrapper->_this, From 77ab5505ab7e98730dcf33628661f31724f566c1 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Tue, 23 Dec 2014 23:36:26 -0600 Subject: [PATCH 26/53] Initial implementation of flow-based destructor call insertion. It compiles. --- compiler/include/passes.h | 1 + compiler/make/Makefile.compiler.head | 2 +- compiler/passes/Makefile.share | 1 + compiler/passes/insertAutoCopyAutoDestroy.cpp | 524 ++++++++++++++++++ compiler/resolution/callDestructors.cpp | 1 + 5 files changed, 528 insertions(+), 1 deletion(-) create mode 100644 compiler/passes/insertAutoCopyAutoDestroy.cpp diff --git a/compiler/include/passes.h b/compiler/include/passes.h index d569f105ae08..7e75e2158108 100644 --- a/compiler/include/passes.h +++ b/compiler/include/passes.h @@ -50,6 +50,7 @@ void expandExternArrayCalls(); void flattenClasses(); void flattenFunctions(); void inlineFunctions(); +void insertAutoCopyAutoDestroy(); void insertLineNumbers(); void insertWideReferences(); void narrowWideReferences(); diff --git a/compiler/make/Makefile.compiler.head b/compiler/make/Makefile.compiler.head index da238db4b76a..b4d407f59df8 100644 --- a/compiler/make/Makefile.compiler.head +++ b/compiler/make/Makefile.compiler.head @@ -34,7 +34,7 @@ endif include $(CHPL_MAKE_HOME)/make/Makefile.base INCL_CFLAGS = -I. -I$(COMPILER_ROOT)/include/$(CHPL_MAKE_HOST_PLATFORM) -I$(COMPILER_ROOT)/include $(LLVM_INCLUDES) -COMP_CFLAGS += $(INCL_CFLAGS) +COMP_CFLAGS += $(INCL_CFLAGS) -DHILDE_MM=1 # # add gc-related stuff diff --git a/compiler/passes/Makefile.share b/compiler/passes/Makefile.share index e91db79c31f5..2ab442f5091b 100644 --- a/compiler/passes/Makefile.share +++ b/compiler/passes/Makefile.share @@ -33,6 +33,7 @@ PASSES_SRCS = \ filesToAST.cpp \ flattenClasses.cpp \ flattenFunctions.cpp \ + insertAutoCopyAutoDestroy.cpp \ insertLineNumbers.cpp \ insertWideReferences.cpp \ normalize.cpp \ diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp new file mode 100644 index 000000000000..3dd057a14b7e --- /dev/null +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -0,0 +1,524 @@ +/* + * Copyright 2004-2014 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// insertAutoCopyAutoDestroy +// +// Inserts copy-constructor and destructor calls as needed, by tracking the +// "ownership" of record objects. +// +//############################################################################# +//# +//# Ownership represents the "liveness" of a record object in the sense that it +//# may contain heap-allocated data. After a record object is constructed, any +//# of its fields of class type may refer to a class object. In that state, any +//# heap-allocated fields in the record are "owned" by the record. Calling the +//# destructor for that record type gives the record author the opportunity to +//# clean up any contained class fields and thereby avoid leaking memory. After +//# the destructor is called, the record variable no longer owns any +//# heap-allocated data, so that data is "unowned". +//# +//# In the simplest view, ownership is a binary state: construction changes the +//# state from false to true; destruction changes the state from true to false. +//# +//# Ownership of a record can also be transferred by a bit-wise copy. When a +//# bit-wise copy of a record is made, class fields (which are implemented as +//# pointers) are copied verbatim. That means that both record objects point to +//# the same class data. Ownership is then shared between them. Some time +//# before both copies go out of scope, one of them needs to call its +//# destructor, the other should not (to avoid double-deletion errors). +//# +//# When ownership is shared in this way, we can perform a sort of internal +//# reference-counting, maintaining a list of aliases, and ensuring that the +//# destructor is called on one of them before the last member of that alias set +//# goes out of scope. +//# +//# For these purposes, we assume that none of the class fields of an alias is +//# updated, so they remain equivalent throughout. If that is not true, then +//# the two variables represent independent variables. In that case, a +//# copy-constructor call should have been inserted because each variable should +//# have its own initialization. So its absence would indicate an error +//# somewhere upstream. +//# +//# We assume that the input AST contains the minimum number of +//# copy-constructor calls required to provide the specified semantics. That +//# is, record values passed by in intent must be copied. We do not yet +//# attempt to pass ownership of an object into a called routine. Two kinds of +//# variable whose ownersihp must be true before exiting a routine are: +//# - The return value variable +//# - A record field in a record. +//# If ownership of any of these is false when they (or their container) is +//# about to go out of scope, a copy must be made to bring ownership to the +//# expected state. Otherwise, ownership can be shared and a copy potentially saved. +//# +//# After copy-constructor calls have been added, the routine has its full +//# complement of construcotr calls. Correct AMM is then just a matter of +//# inserting the minimum number of destructor calls to drive the ownership of +//# all local variables to false before the routine ends. Dataflow analysis is +//# used to propagate this constraint backwards, in case ownership is +//# transferred out of a variable along one path but not another. +//# +//############################################################################# + +// The sets used for flow analysis are: +// GEN -- The set of symbols whose ownership transitions to true by the end of +// the block. +// KILL -- The set of symbols whose ownership transitions to false by the end +// of the block. +// IN -- The set of symbols that are owned at the beginning of the block. +// OUT -- The set of symbols that are owned at the end of the block. +// +// GEN[i] contains a true bit corresponding to each symbol that is constructed +// in that block. We expect that symbol is constructed only once, so if IN[i] +// and GEN[i] have a true bit in the same position, we can flag an internal +// error. This condition is unlikely, so at best it should be added as a +// verify check. +// +// KILL[i] contains a true bit corresponding to each symbol that is destroyed +// or whose ownership is usurped in that block. We keep a separate list of +// aliases. When a bitwise copy is made, we treat the new copy as if it has +// been constructed without changing the state of the original. When any +// member of that set of aliases is destroyed or usurped, all members of the +// set are added to KILL[i]. +// +// We start by performing forward flow analysis to establish the value of IN. +// IN[i] is the intersection of OUT[pred(i)] for all predecessors of block i. +// It is desirable to allow ownership to flow through to the end of the +// function (to a final block with many predecessors). That way, we can +// consume liveness with a minimum of destructor calls. However, if we cannot +// depend on a given symbol being owned at the beginning of a block, then its +// destructor must be pushed back up the flow graph. For example, we might +// pass ownership to a field or global variable on one path; then we need a +// destructor call to get rid of ownership on the other path(s). +// +// For a block that contains a constructor followed by a destructor, both +// GEN[i] and KILL[i] will be zero. If they are both true, that represents a +// symbol being destroyed and then reused, so GEN takes precedence over KILL +// in forward flow: OUT[i] = IN[i] - KILL[i] + GEN[i]. +// +// For backward flow, we compute OUT[i] as the intersection of IN[succ(i)] for +// all successors of block i. (No information is propagated backward through +// the blocks, so this can be done in one iteration.) Then, in the local +// traversal the follows, for any symbol that remains owned at the end of block +// i where its OUT[i] is false, we add a destructor call to make that condition +// true. +// + +#include "passes.h" // The main routine is declared here. + +#include "bb.h" +#include "expr.h" +#include "symbol.h" +#include "bitVec.h" +#include "stlUtil.h" +#include "astutil.h" +#include "resolution.h" // For AutoDestroyMap. + +#include +#include + +typedef std::vector FlowSet; +typedef std::vector SymbolVector; +typedef std::map SymbolIndexMap; +typedef SymbolIndexMap::value_type SymbolIndexElement; +typedef std::map AliasVectorMap; +typedef AliasVectorMap::value_type AliasMapElement; +typedef std::vector SymExprVector; +typedef std::vector DefExprVector; + + +static void createFlowSet(std::vector& set, + size_t nbbs, + size_t size) +{ + // Create a BitVec of length size for each block. + for (size_t i = 0; i < nbbs; ++i) + set.push_back(new BitVec(size)); +} + + +static void destroyFlowSet(std::vector set) +{ + for_vector(BitVec, vec, set) + delete vec, vec = 0; +} + + +// An alias vector map is map from symbols to lists of symbols, but all of the +// symbols in the same clique share the same list. So, to destroy all the +// lists in the map, we find the list through the first member of the clique, +// then set the map element for each member in the list to NULL. (The list +// contains the element though which the list is accessed, so this detaches the +// list from all map elements.) Then we delete the list. (The symbols +// contained in the list are owned elsewhere.) +static void destroyAliasVectorMap(AliasVectorMap& aliases) +{ + for (AliasVectorMap::iterator it = aliases.begin(); + it != aliases.end(); ++it) + { + if (it->second == NULL) + continue; + + SymbolVector* aliasList = it->second; + for_vector(Symbol, sym, *aliasList) + aliases[sym] = NULL; + + delete aliasList; + } +} + + +// Scans the body of the given function and inserts all of the variable and +// argument symbols it finds into the vector of symbols. Bits in the flow +// analysis bit-vectors correspond to the entries in this vector. +// Also constructs an index map, to make it easier to find the index of a +// symbol in the vector. (Otherwise, a linear search is required.) +static void +extractSymbols(FnSymbol* fn, + SymbolVector& symbols, SymbolIndexMap& symbolIndex) +{ + DefExprVector defExprs; + collectDefExprsSTL(fn, defExprs); + + for_vector(DefExpr, def, defExprs) + { + Symbol* sym = def->sym; + symbolIndex.insert(SymbolIndexElement(sym, symbols.size())); + symbols.push_back(sym); + + // We expect the symbolIndex to return the index of that symbol in the + // symbols vector. + INT_ASSERT(symbols[symbolIndex[sym]] == sym); + } +} + + +static void processConstructor(CallExpr* call, SymExpr* se, + BitVec* gen, AliasVectorMap& aliases, + const SymbolIndexMap& symbolIndex) +{ + // In the current incarnation, we expect construction to look like: + // ('move' lhs (construct )) + // When constructors turn into methods, this will look a bit different. + if (call->isPrimitive(PRIM_MOVE) || + call->isPrimitive(PRIM_ASSIGN)) + { + if (CallExpr* rhsCall = toCallExpr(call->get(2))) + { + if (FnSymbol* fn = rhsCall->isResolved()) + { + if (fn->hasFlag(FLAG_CONSTRUCTOR) || + fn->hasFlag(FLAG_INIT_COPY_FN) || + fn->hasFlag(FLAG_AUTO_COPY_FN)) + { + Symbol* sym = se->var; + size_t index = symbolIndex.at(sym); + // We expect that each symbol gets constructed only once, so if we are + // about to set a bit in the gen set, it cannot already be true. + INT_ASSERT(gen->get(index) == false); + // If this assumption turns out to be false, it means we are reusing + // symbols. That case can be accommodated, but it means we have to + // insert a destructor call ahead of the symbol's reinitialization. + gen->set(index); + // If we start the alias list with the first entry, then we can process + // it unequivocally. + SymbolVector* aliasList = new SymbolVector(); + aliasList->push_back(sym); + aliases.insert(AliasMapElement(sym, aliasList)); + } + } + } + } +} + + +// Add the alias to the end of the alias list accessible through the original +// symbol, and then share the alias list withs the alias. +static void addAlias(Symbol* orig, Symbol* alias, + AliasVectorMap& aliases) +{ + SymbolVector* aliasList = aliases[orig]; + // Implicit assertion that aliasList is non-NULL. + aliasList->push_back(alias); + aliases.insert(AliasMapElement(alias, aliasList)); +} + + +static void processMove(CallExpr* call, SymExpr* se, BitVec* gen, AliasVectorMap& aliases, + const SymbolIndexMap& symbolIndex) +{ + // We only care about bitwise copies here. + if (call->isPrimitive(PRIM_MOVE) || + call->isPrimitive(PRIM_ASSIGN)) + { + // We'll key off the LHS. + // Meaning that we will only pay attention to the expression if the SymExpr + // is in the LHS position in the primitive. + if (SymExpr* lhs = toSymExpr(call->get(1))) + { + if (lhs == se) + { + // We only care about bitwise copies from one symbol to another. + if (SymExpr* rhs = toSymExpr(call->get(2))) + { + Symbol* lsym = lhs->var; + Symbol* rsym = rhs->var; + size_t lindex = symbolIndex.at(lsym); + size_t rindex = symbolIndex.at(rsym); + // Copy ownership state from RHS. + INT_ASSERT(gen->get(lindex) == false); + gen->copy(lindex, gen->get(rindex)); + addAlias(lsym, rsym, aliases); + } + } + } + } +} + + +static void processDestructor(SymExpr* se, + BitVec* kill, AliasVectorMap& aliases, + const SymbolIndexMap& symbolIndex) +{ + // Add all members of an alias clique to the kill set. + // All members of an alias clique point to the same SymbolVector, so we only + // need to look up one arbitrarily and then run the list. + Symbol* sym = se->var; + SymbolVector& aliasList = *aliases[sym]; + for_vector(Symbol, alias, aliasList) + { + size_t index = symbolIndex.at(alias); + // We expect a symbol to be live when it is killed. + INT_ASSERT(kill->get(index) == false); + kill->set(index); + } + + // We don't bother updating the alias list under the assumption that + // symbols are not reused. If that turns out to be false, we have to + // remove the alias clique from aliases here. +} + + +// If this call acts like a destructor, then add the symbols it affects to the +// kill set. +static void processDestructor(CallExpr* call, SymExpr* se, BitVec* kill, + AliasVectorMap& aliases, + const SymbolIndexMap& symbolIndex) +{ + if (FnSymbol* fn = call->isResolved()) + { + // This is a function call. + + // The only one we're interested in right now is a destructor call. + if (fn->hasFlag(FLAG_DESTRUCTOR)) + { + // Paranoid check: This SymExpr is the thing being destroyed, right? + INT_ASSERT(call->get(1) == se); + + processDestructor(se, kill, aliases, symbolIndex); + } + } + else + { + // This is a primitive. + if (call->isPrimitive(PRIM_RETURN)) + { + // Returns act like destructors. + processDestructor(se, kill, aliases, symbolIndex); + } + } +} + + +static void computeTransitions(SymExprVector& symExprs, + BitVec* gen, BitVec* kill, + AliasVectorMap& aliases, + const SymbolIndexMap& symbolIndex) +{ + for_vector(SymExpr, se, symExprs) + { + // We are only interested in call expressions involving the SymExpr. + if (CallExpr* call = toCallExpr(se->parentExpr)) + { + processConstructor(call, se, gen, aliases, symbolIndex); + processMove(call, se, gen, aliases, symbolIndex); + processDestructor(call, se, kill, aliases, symbolIndex); + } + } +} + + +// Look for expressions that create, destroy or transfer ownership of a record +// object. +// Right now, we can get away with just looking for MOVE or ASSIGN primitives +// to determine where ownership is created or transferred. When constructors +// become methods, we'll key off the CONSTRUCTOR flag and modify the state of +// the first (receiver) argument. +static void computeTransitions(BasicBlock& bb, BitVec* gen, BitVec* kill, + AliasVectorMap& aliases, + const SymbolIndexMap& symbolIndex) +{ + for_vector(Expr, expr, bb.exprs) + { + // Are all expressions in this list mutually exclusive? + // Can we speed things up by processing only statement expressions? + + SymExprVector symExprs; + collectSymExprsSTL(expr, symExprs); + + computeTransitions(symExprs, gen, kill, aliases, symbolIndex); + } +} + + +static void computeTransitions(FnSymbol* fn, + FlowSet& GEN, FlowSet& KILL, + AliasVectorMap& aliases, + const SymbolIndexMap& symbolIndex) +{ + size_t nbbs = fn->basicBlocks->size(); + for (size_t i = 0; i < nbbs; ++i) + { + computeTransitions(*(*fn->basicBlocks)[i], GEN[i], KILL[i], aliases, symbolIndex); + } +} + + +// This predicate determines if the given statement is a jump. +static bool isJump(Expr* stmt) +{ + // A goto is definitely a jump. + if (isGotoStmt(stmt)) + return true; + + // A return primitive works like a jump. (Nothing should appear after it.) + if (CallExpr* call = toCallExpr(stmt)) + if (call->isPrimitive(PRIM_RETURN)) + return true; + + return false; +} + + +// At the end of this basic block, insert an autodestroy for each symbol +// specified by the given bit-vector. +static void insertAutoDestroy(BasicBlock& bb, BitVec* to_kill, + const SymbolVector& symbols) +{ + // Find the last statement in the block. + Expr*& last = bb.exprs.back(); + Expr* stmt = last->getStmtExpr(); + bool isjump = isJump(stmt); + + // For each true bit in the bit vector, add an autodestroy call. + for (size_t j = 0; j < to_kill->size(); ++j) + { + if (to_kill->get(j)) + { + Symbol* sym = symbols[j]; + CallExpr* autoDestroyCall = new CallExpr(autoDestroyMap.get(sym->type), sym); + if (isjump) + stmt->insertBefore(autoDestroyCall); + else + stmt->insertAfter(autoDestroyCall); + } + } +} + +static void insertAutoDestroy(FnSymbol* fn, + FlowSet& GEN, FlowSet& KILL, + FlowSet& IN, FlowSet& OUT, + const SymbolVector& symbols) +{ + size_t nbbs = fn->basicBlocks->size(); + for (size_t i = 0; i < nbbs; i++) + { + // We need to insert an autodestroy call for each symbol that is owned + // (live) at the end of the block but is unowned (dead) in the OUT set. + BasicBlock& bb = *(*fn->basicBlocks)[i]; + BitVec* to_kill = *IN[i] + *GEN[i] - *KILL[i] - *OUT[i]; + insertAutoDestroy(bb, to_kill, symbols); + delete to_kill; to_kill = 0; + } +} + +static void insertAutoDestroy(FnSymbol* fn) +{ + BasicBlock::buildBasicBlocks(fn); + size_t nbbs = fn->basicBlocks->size(); + + SymbolVector symbols; + SymbolIndexMap symbolIndex; + extractSymbols(fn, symbols, symbolIndex); + + size_t size = symbols.size(); + +#if DEBUG_AMM + if (debug > 0) + { + printf("\n"); + list_view(fn); + + BasicBlock::printBasicBlocks(fn); + } +#endif + + FlowSet GEN; + FlowSet KILL; + FlowSet IN; + FlowSet OUT; + + createFlowSet(GEN, nbbs, size); + createFlowSet(KILL, nbbs, size); + createFlowSet(IN, nbbs, size); + createFlowSet(OUT, nbbs, size); + + AliasVectorMap aliases; + + computeTransitions(fn, GEN, KILL, aliases, symbolIndex); + + BasicBlock::forwardFlowAnalysis(fn, GEN, KILL, IN, OUT, true); + + insertAutoDestroy(fn, GEN, KILL, IN, OUT, symbols); + + destroyAliasVectorMap(aliases); + + destroyFlowSet(GEN); + destroyFlowSet(KILL); + destroyFlowSet(IN); + destroyFlowSet(OUT); +} + + +static void insertAutoDestroy() +{ + forv_Vec(FnSymbol, fn, gFnSymbols) + { + insertAutoDestroy(fn); + } +} + + +void insertAutoCopyAutoDestroy() +{ + // First of all, just insert autoDestroy calls. + // We assume that all necessary autoCopy calls have been added. + // As an enhancement/optimization, we'll add a separate pass to compute and + // add the minimum number of autoCopy calls required. + insertAutoDestroy(); +} diff --git a/compiler/resolution/callDestructors.cpp b/compiler/resolution/callDestructors.cpp index 8727309064c0..5b1c7e6f1523 100644 --- a/compiler/resolution/callDestructors.cpp +++ b/compiler/resolution/callDestructors.cpp @@ -928,4 +928,5 @@ callDestructors() { insertGlobalAutoDestroyCalls(); #endif insertReferenceTemps(); +// insertAutoCopyAutoDestroy(); } From 85edee4cf7eb88ec3c139fc2f8fdd9e4d74b07b7 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Sat, 27 Dec 2014 11:07:27 -0600 Subject: [PATCH 27/53] Runs hello.chpl. Added filters in extractSymbols() to ignore symbols that are not VarSymbols or ArgSymbols, extern symbols and symbols not of a record type. Filtering out symbols of class type also filters out references. Maybe this is too clever, but at least fits well with the style of programming currently in place. If the representation of reference types changes in the future, this is one place where the code will have to be amended. At least that case is covered by a helpful comment. Moved the initialization of the alias table up into extractSymbols() because the expectation that each symbol is initialized before being copied (or copied into) through a move is currently false. Split off the isConstructor() test as a predicate. Added a test in processMove(), so we can flag when an uninitialized symbol is copied. This would probably result in a large number of false positives before flow analysis is carried out, because each block starts with an empty gen set. We now filter by the members of the symbolIndex, so we ignore symbols that are not local (including module-level and extern symbols) and not variables or arguments. Had to add a test to skip degenerate basic blocks. Why do we still have degenerate basic blocks? We skip functions that are prototypes (including extern functions) altogether. Re-enabled insertGlobalAutoDestroyCalls() and activated the new insertAutoDestroyCalls() mini-pass. --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 166 ++++++++++++++---- compiler/resolution/callDestructors.cpp | 9 +- 2 files changed, 131 insertions(+), 44 deletions(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index 3dd057a14b7e..ac234f8fe4cd 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -17,6 +17,9 @@ * limitations under the License. */ +// TODO: This should be moved to symbol.cpp +bool fWarnOwnership = false; + // insertAutoCopyAutoDestroy // // Inserts copy-constructor and destructor calls as needed, by tracking the @@ -184,13 +187,18 @@ static void destroyAliasVectorMap(AliasVectorMap& aliases) // Scans the body of the given function and inserts all of the variable and -// argument symbols it finds into the vector of symbols. Bits in the flow +// argument symbols defined in it into the vector of symbols. Bits in the flow // analysis bit-vectors correspond to the entries in this vector. // Also constructs an index map, to make it easier to find the index of a // symbol in the vector. (Otherwise, a linear search is required.) +// The alias map can also be populated at the same time. +// TODO: Split the alias list off as a separate class, mostly to make it easy +// to document the data structure it implements. static void extractSymbols(FnSymbol* fn, - SymbolVector& symbols, SymbolIndexMap& symbolIndex) + SymbolVector& symbols, + SymbolIndexMap& symbolIndex, + AliasVectorMap& aliases) { DefExprVector defExprs; collectDefExprsSTL(fn, defExprs); @@ -198,18 +206,80 @@ extractSymbols(FnSymbol* fn, for_vector(DefExpr, def, defExprs) { Symbol* sym = def->sym; + + // We are interested only in arguments and variables. + if (! (toArgSymbol(sym) || toVarSymbol(sym))) + continue; + + Type* type = sym->type; + + // TODO: Extern record types also do not have constructors and + // destructors. To treat them uniformly, we would have to enforce that + // extern types supply constructors and destructors and/or supply them + // internally. + TypeSymbol* ts = type->symbol; + if (ts->hasFlag(FLAG_EXTERN)) + continue; + + // We are concerned only with record types. + // TODO: This is too bad, because it would be nice to be able to treat all + // value types uniformly. But for that to work, arguments of fundamental + // type must be constructed by having their values piped through a copy + // constructor. Currently, that is not the case. + + // We are interested only in records passed by value. Records passed by + // ref appear be a class in the current AST because _ref(T) is a class type. + AggregateType* at = toAggregateType(type); + if (at == NULL) + // Not an aggregate type, so not a record. + continue; + if (!at->isRecord()) + // Not a record. + continue; + symbolIndex.insert(SymbolIndexElement(sym, symbols.size())); symbols.push_back(sym); // We expect the symbolIndex to return the index of that symbol in the // symbols vector. INT_ASSERT(symbols[symbolIndex[sym]] == sym); + + // Initialize each entry in the alias map with a list of symbols + // containingh the symbol itself. + SymbolVector* aliasList = new SymbolVector(); + aliasList->push_back(sym); + aliases.insert(AliasMapElement(sym, aliasList)); + } +} + + +// Returns true if this function returns a fully-constructed value; false otherwise. +static bool isConstructor(CallExpr* call) +{ + if (FnSymbol* fn = call->isResolved()) + { + // A "normal" function. + + // Return values of class type are ruled out. + Type* retType = fn->retType; + if (AggregateType* at = toAggregateType(retType)) + if (at->isClass()) + return false; } + else + { + // A primitive. + if (AggregateType* at = toAggregateType(call->typeInfo())) + if (at->isClass()) + return false; + } + + return true; } static void processConstructor(CallExpr* call, SymExpr* se, - BitVec* gen, AliasVectorMap& aliases, + BitVec* gen, const SymbolIndexMap& symbolIndex) { // In the current incarnation, we expect construction to look like: @@ -220,42 +290,37 @@ static void processConstructor(CallExpr* call, SymExpr* se, { if (CallExpr* rhsCall = toCallExpr(call->get(2))) { - if (FnSymbol* fn = rhsCall->isResolved()) + if (isConstructor(rhsCall)) { - if (fn->hasFlag(FLAG_CONSTRUCTOR) || - fn->hasFlag(FLAG_INIT_COPY_FN) || - fn->hasFlag(FLAG_AUTO_COPY_FN)) - { - Symbol* sym = se->var; - size_t index = symbolIndex.at(sym); - // We expect that each symbol gets constructed only once, so if we are - // about to set a bit in the gen set, it cannot already be true. - INT_ASSERT(gen->get(index) == false); - // If this assumption turns out to be false, it means we are reusing - // symbols. That case can be accommodated, but it means we have to - // insert a destructor call ahead of the symbol's reinitialization. - gen->set(index); - // If we start the alias list with the first entry, then we can process - // it unequivocally. - SymbolVector* aliasList = new SymbolVector(); - aliasList->push_back(sym); - aliases.insert(AliasMapElement(sym, aliasList)); - } + // Any function returning a value is considered to be a constructor. + Symbol* sym = se->var; + size_t index = symbolIndex.at(sym); + // We expect that each symbol gets constructed only once, so if we are + // about to set a bit in the gen set, it cannot already be true. + INT_ASSERT(gen->get(index) == false); + // If this assumption turns out to be false, it means we are reusing + // symbols. That case can be accommodated, but it means we have to + // insert a destructor call ahead of the symbol's reinitialization. + gen->set(index); } } } } -// Add the alias to the end of the alias list accessible through the original -// symbol, and then share the alias list withs the alias. -static void addAlias(Symbol* orig, Symbol* alias, - AliasVectorMap& aliases) +// Merge the alias lists of two symbols that have become aliases. +static void mergeAliases(Symbol* orig, Symbol* alias, + AliasVectorMap& aliases) { - SymbolVector* aliasList = aliases[orig]; - // Implicit assertion that aliasList is non-NULL. - aliasList->push_back(alias); - aliases.insert(AliasMapElement(alias, aliasList)); + SymbolVector* origList = aliases[orig]; + SymbolVector* aliasList = aliases[alias]; + for (size_t i = 0; i < aliasList->size(); ++i) + { + Symbol* sym = aliasList->at(i); + origList->push_back(sym); + } + delete aliasList; + aliases[alias] = origList; } @@ -282,8 +347,17 @@ static void processMove(CallExpr* call, SymExpr* se, BitVec* gen, AliasVectorMap size_t rindex = symbolIndex.at(rsym); // Copy ownership state from RHS. INT_ASSERT(gen->get(lindex) == false); - gen->copy(lindex, gen->get(rindex)); - addAlias(lsym, rsym, aliases); + if (!gen->get(rindex)) + { + if (fWarnOwnership) + USR_WARN(rsym, "Uninitialized symbol is copied here"); + } + else + { + gen->set(lindex); + } + // Merge aliases whether or not they are live. + mergeAliases(rsym, lsym, aliases); } } } @@ -299,8 +373,9 @@ static void processDestructor(SymExpr* se, // All members of an alias clique point to the same SymbolVector, so we only // need to look up one arbitrarily and then run the list. Symbol* sym = se->var; - SymbolVector& aliasList = *aliases[sym]; - for_vector(Symbol, alias, aliasList) + SymbolVector* aliasList = aliases[sym]; + + for_vector(Symbol, alias, *aliasList) { size_t index = symbolIndex.at(alias); // We expect a symbol to be live when it is killed. @@ -352,10 +427,16 @@ static void computeTransitions(SymExprVector& symExprs, { for_vector(SymExpr, se, symExprs) { + // We are only interested in local symbols, so if this one does not appear + // in our map, move on. + Symbol* sym = se->var; + if (symbolIndex.find(sym) == symbolIndex.end()) + continue; + // We are only interested in call expressions involving the SymExpr. if (CallExpr* call = toCallExpr(se->parentExpr)) { - processConstructor(call, se, gen, aliases, symbolIndex); + processConstructor(call, se, gen, symbolIndex); processMove(call, se, gen, aliases, symbolIndex); processDestructor(call, se, kill, aliases, symbolIndex); } @@ -420,6 +501,10 @@ static bool isJump(Expr* stmt) static void insertAutoDestroy(BasicBlock& bb, BitVec* to_kill, const SymbolVector& symbols) { + // Skip degenerate basic blocks. + if (bb.exprs.size() == 0) + return; + // Find the last statement in the block. Expr*& last = bb.exprs.back(); Expr* stmt = last->getStmtExpr(); @@ -464,7 +549,8 @@ static void insertAutoDestroy(FnSymbol* fn) SymbolVector symbols; SymbolIndexMap symbolIndex; - extractSymbols(fn, symbols, symbolIndex); + AliasVectorMap aliases; + extractSymbols(fn, symbols, symbolIndex, aliases); size_t size = symbols.size(); @@ -488,8 +574,6 @@ static void insertAutoDestroy(FnSymbol* fn) createFlowSet(IN, nbbs, size); createFlowSet(OUT, nbbs, size); - AliasVectorMap aliases; - computeTransitions(fn, GEN, KILL, aliases, symbolIndex); BasicBlock::forwardFlowAnalysis(fn, GEN, KILL, IN, OUT, true); @@ -509,6 +593,10 @@ static void insertAutoDestroy() { forv_Vec(FnSymbol, fn, gFnSymbols) { + // Function prototypes have no body, so we skip them. + if (fn->hasFlag(FLAG_FUNCTION_PROTOTYPE)) + continue; + insertAutoDestroy(fn); } } diff --git a/compiler/resolution/callDestructors.cpp b/compiler/resolution/callDestructors.cpp index 5b1c7e6f1523..80d958bf563a 100644 --- a/compiler/resolution/callDestructors.cpp +++ b/compiler/resolution/callDestructors.cpp @@ -761,7 +761,7 @@ fixupDestructors() { } } -#ifndef HILDE_MM + static void insertGlobalAutoDestroyCalls() { // --ipe does not build chpl_gen_main if (chpl_gen_main == NULL) @@ -786,7 +786,7 @@ static void insertGlobalAutoDestroyCalls() { } fn->insertAtTail(new CallExpr(PRIM_RETURN, gVoid)); } -#endif + static void insertDestructorCalls() { @@ -922,11 +922,10 @@ callDestructors() { cullExplicitAutoDestroyFlags(); insertAutoDestroyCalls(); returnRecordsByReferenceArguments(); +#else + insertAutoCopyAutoDestroy(); #endif insertYieldTemps(); -#ifndef HILDE_MM insertGlobalAutoDestroyCalls(); -#endif insertReferenceTemps(); -// insertAutoCopyAutoDestroy(); } From ae8a4f32948fe6356c9827bb7128fce2072f1e1d Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Sun, 28 Dec 2014 16:53:06 -0600 Subject: [PATCH 28/53] Rework alias merge. Down to 449 regressions. --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index ac234f8fe4cd..846a97dd0808 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -318,9 +318,9 @@ static void mergeAliases(Symbol* orig, Symbol* alias, { Symbol* sym = aliasList->at(i); origList->push_back(sym); + aliases[sym] = origList; } delete aliasList; - aliases[alias] = origList; } From 54dd49e5afb6644b730ab3368fc1ffd0ffbe315c Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Mon, 29 Dec 2014 01:02:01 -0600 Subject: [PATCH 29/53] Handle some special cases in alias tracking. If alias lists are the same, do not merge them again. The RHS symbol of a move is not necessarily a local symbol. It can be a module-level or extern symbol. Now down to 55 regresssions. --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index 846a97dd0808..6bf6619d0229 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -314,6 +314,13 @@ static void mergeAliases(Symbol* orig, Symbol* alias, { SymbolVector* origList = aliases[orig]; SymbolVector* aliasList = aliases[alias]; + + if (aliasList == origList) + { + // These two symbols are already aliases. + return; + } + for (size_t i = 0; i < aliasList->size(); ++i) { Symbol* sym = aliasList->at(i); @@ -344,7 +351,16 @@ static void processMove(CallExpr* call, SymExpr* se, BitVec* gen, AliasVectorMap Symbol* lsym = lhs->var; Symbol* rsym = rhs->var; size_t lindex = symbolIndex.at(lsym); - size_t rindex = symbolIndex.at(rsym); + SymbolIndexMap::const_iterator rsymPair = symbolIndex.find(rsym); + if (rsymPair == symbolIndex.end()) + { + // TODO: A move from a global or extern symbol without a copy-constructor + // call is probably an AST consistency or codegen error, given that + // we have already weeded out fundamental, extern and class types. + return; + } + size_t rindex = rsymPair->second; + // Copy ownership state from RHS. INT_ASSERT(gen->get(lindex) == false); if (!gen->get(rindex)) From 414406ccb1b50e22e105d65215d95bcb397c05e7 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Mon, 29 Dec 2014 11:28:06 -0600 Subject: [PATCH 30/53] Move insertAutoCopyAutoDestroy pass after lowerIterators. Down to 46 regressions. --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 4 +++- compiler/resolution/callDestructors.cpp | 2 -- compiler/resolution/lowerIterators.cpp | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index 6bf6619d0229..d373d52541a8 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -297,7 +297,9 @@ static void processConstructor(CallExpr* call, SymExpr* se, size_t index = symbolIndex.at(sym); // We expect that each symbol gets constructed only once, so if we are // about to set a bit in the gen set, it cannot already be true. - INT_ASSERT(gen->get(index) == false); + if (gen->get(index)) + if (fWarnOwnership) + USR_WARN(sym, "Reinitialization of sym"); // If this assumption turns out to be false, it means we are reusing // symbols. That case can be accommodated, but it means we have to // insert a destructor call ahead of the symbol's reinitialization. diff --git a/compiler/resolution/callDestructors.cpp b/compiler/resolution/callDestructors.cpp index 80d958bf563a..6a7141d3613b 100644 --- a/compiler/resolution/callDestructors.cpp +++ b/compiler/resolution/callDestructors.cpp @@ -922,8 +922,6 @@ callDestructors() { cullExplicitAutoDestroyFlags(); insertAutoDestroyCalls(); returnRecordsByReferenceArguments(); -#else - insertAutoCopyAutoDestroy(); #endif insertYieldTemps(); insertGlobalAutoDestroyCalls(); diff --git a/compiler/resolution/lowerIterators.cpp b/compiler/resolution/lowerIterators.cpp index 6e52282182b1..9b5a5bcd65c3 100644 --- a/compiler/resolution/lowerIterators.cpp +++ b/compiler/resolution/lowerIterators.cpp @@ -2195,5 +2195,9 @@ void lowerIterators() { reconstructIRautoCopyAutoDestroy(); cleanupTemporaryVectors(); + +#ifdef HILDE_MM + insertAutoCopyAutoDestroy(); +#endif } From 235452b7e716981c24c3d080c836a780b94ec61d Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Wed, 31 Dec 2014 02:27:43 -0600 Subject: [PATCH 31/53] Add typedefs to the head of bb.h. I thought I would need these but I don't. But this follows the "single point of definition" ROT, so I kept it. --- compiler/include/bb.h | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/compiler/include/bb.h b/compiler/include/bb.h index 61ba9cd51378..d3e49c0a5ec6 100644 --- a/compiler/include/bb.h +++ b/compiler/include/bb.h @@ -36,6 +36,13 @@ class SymExpr; // The goto and label maps persist only between calls to buildBasicBlocks. class BasicBlock { + // + // Typedefs + // + public: + typedef std::vector BasicBlockVector; + typedef std::vector BitVecVector; + // // Class methods/variables // @@ -51,16 +58,16 @@ class BasicBlock Map& localMap); static void backwardFlowAnalysis(FnSymbol* fn, - std::vector& GEN, - std::vector& KILL, - std::vector& IN, - std::vector& OUT); + BitVecVector& GEN, + BitVecVector& KILL, + BitVecVector& IN, + BitVecVector& OUT); static void forwardFlowAnalysis (FnSymbol* fn, - std::vector& GEN, - std::vector& KILL, - std::vector& IN, - std::vector& OUT, + BitVecVector& GEN, + BitVecVector& KILL, + BitVecVector& IN, + BitVecVector& OUT, bool intersect = true); static void printLocalsVector(Vec locals, @@ -69,10 +76,10 @@ class BasicBlock static void printDefsVector(std::vector defs, Map& defMap); - static void printLocalsVectorSets(std::vector& sets, + static void printLocalsVectorSets(BitVecVector& sets, Vec locals); - static void printBitVectorSets(std::vector& sets); + static void printBitVectorSets(BitVecVector& sets); static BasicBlock* basicBlock; @@ -80,7 +87,7 @@ class BasicBlock BasicBlock*> labelMaps; static Map*> gotoMaps; + BasicBlockVector*> gotoMaps; private: static void buildBasicBlocks(FnSymbol* fn, @@ -109,8 +116,8 @@ class BasicBlock std::vector exprs; std::vector marks; - std::vector ins; - std::vector outs; + BasicBlockVector ins; + BasicBlockVector outs; private: bool isOK(); From 2aaad4725b38767576258c81fe66142198c24853 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Wed, 31 Dec 2014 02:29:28 -0600 Subject: [PATCH 32/53] Added operator synonyms for conjunction (&=) and disjunction (|=). --- compiler/include/bitVec.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/include/bitVec.h b/compiler/include/bitVec.h index 50138d117f9e..cd0d671c49b4 100644 --- a/compiler/include/bitVec.h +++ b/compiler/include/bitVec.h @@ -36,7 +36,10 @@ class BitVec { void disjunction(const BitVec& other); void intersection(const BitVec& other); - + // Synonyms for disjunction (union) and (conjunction) intersection above. + void operator|=(BitVec& other) { this->disjunction(other); } + void operator&=(BitVec& other) { this->intersection(other); } + // Added functionality to make this compatible with std::bitset and thus // boosts dynamic bitset if that gets into the STL, or we start using boost bool equals(const BitVec& other) const; From e99b6a4d63e6822b340f1de3b50741077967df81 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Wed, 31 Dec 2014 02:32:21 -0600 Subject: [PATCH 33/53] Added back "insert auto copy" flags in resolution. --- compiler/resolution/functionResolution.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/resolution/functionResolution.cpp b/compiler/resolution/functionResolution.cpp index 3bb5095d5338..2ab68d6d2640 100644 --- a/compiler/resolution/functionResolution.cpp +++ b/compiler/resolution/functionResolution.cpp @@ -5514,17 +5514,17 @@ postFold(Expr* expr) { } } if (!set) { -#ifndef HILDE_MM if (lhs->var->hasFlag(FLAG_EXPR_TEMP) && !lhs->var->hasFlag(FLAG_TYPE_VARIABLE)) { if (CallExpr* rhsCall = toCallExpr(call->get(2))) { if (requiresImplicitDestroy(rhsCall)) { lhs->var->addFlag(FLAG_INSERT_AUTO_COPY); +#ifndef HILDE_MM lhs->var->addFlag(FLAG_INSERT_AUTO_DESTROY); +#endif } } } -#endif if (isReferenceType(lhs->var->type) || lhs->var->type->symbol->hasFlag(FLAG_REF_ITERATOR_CLASS) || lhs->var->type->symbol->hasFlag(FLAG_ARRAY)) From 7c773b249f00913975fe75151e3493b8eab2aead Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Wed, 31 Dec 2014 02:33:37 -0600 Subject: [PATCH 34/53] Moved insertAutoCopyAutoDestroy pass to start of parallel. --- compiler/passes/parallel.cpp | 12 ++++++++++++ compiler/resolution/lowerIterators.cpp | 4 ---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/compiler/passes/parallel.cpp b/compiler/passes/parallel.cpp index 2e6feca526a8..124370eebfd3 100644 --- a/compiler/passes/parallel.cpp +++ b/compiler/passes/parallel.cpp @@ -1100,6 +1100,18 @@ reprivatizeIterators() { void parallel(void) { + +#ifdef HILDE_MM + // This is here just because it depends on the cleanup after lowerIterators + // having been performed, and it depends on lower iterators to get the basic + // block structure of iterator functions right. + // In other words, it could be moved back to the end of callDestructors.cpp + // if basic block analysis were modified to treat a PRIM_YIELD as an + // end-of-block and treat all blocks within an iterator function as + // successors of a faked-in start block. + insertAutoCopyAutoDestroy(); +#endif + Vec taskFunctions; // Collect the task functions for processing. diff --git a/compiler/resolution/lowerIterators.cpp b/compiler/resolution/lowerIterators.cpp index 9b5a5bcd65c3..6e52282182b1 100644 --- a/compiler/resolution/lowerIterators.cpp +++ b/compiler/resolution/lowerIterators.cpp @@ -2195,9 +2195,5 @@ void lowerIterators() { reconstructIRautoCopyAutoDestroy(); cleanupTemporaryVectors(); - -#ifdef HILDE_MM - insertAutoCopyAutoDestroy(); -#endif } From 1864ef86c2655faca3bd439972e522ec6f81ccb0 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Wed, 31 Dec 2014 02:38:11 -0600 Subject: [PATCH 35/53] Now actually adding autoDestroy calls. Added a backward flow pass to propagate disharge-of-ownership constraints from IN sets back across the gap to OUT sets. The OUT sets of terminal blocks are set to all zeroes to force all locals to be autodestroyed on exit. Added code so that we only add an autoDestroy for one member of an alias clique. --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 96 +++++++++++++++++-- 1 file changed, 86 insertions(+), 10 deletions(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index d373d52541a8..8a360fec7b39 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -20,6 +20,10 @@ // TODO: This should be moved to symbol.cpp bool fWarnOwnership = false; +// TODO: Remove this #define for normal compilation +#define DEBUG_AMM 1 +static int debug = 0; + // insertAutoCopyAutoDestroy // // Inserts copy-constructor and destructor calls as needed, by tracking the @@ -67,14 +71,15 @@ bool fWarnOwnership = false; //# - A record field in a record. //# If ownership of any of these is false when they (or their container) is //# about to go out of scope, a copy must be made to bring ownership to the -//# expected state. Otherwise, ownership can be shared and a copy potentially saved. +//# expected state. Otherwise, ownership can be shared and a copy potentially +//# avoided. //# //# After copy-constructor calls have been added, the routine has its full //# complement of construcotr calls. Correct AMM is then just a matter of //# inserting the minimum number of destructor calls to drive the ownership of //# all local variables to false before the routine ends. Dataflow analysis is //# used to propagate this constraint backwards, in case ownership is -//# transferred out of a variable along one path but not another. +//# transferred out of a block along one path but not another. //# //############################################################################# @@ -118,8 +123,8 @@ bool fWarnOwnership = false; // all successors of block i. (No information is propagated backward through // the blocks, so this can be done in one iteration.) Then, in the local // traversal the follows, for any symbol that remains owned at the end of block -// i where its OUT[i] is false, we add a destructor call to make that condition -// true. +// i where its OUT[i] is false, we add a destructor call so that the results of +// forward and backward flow agree. // #include "passes.h" // The main routine is declared here. @@ -130,6 +135,7 @@ bool fWarnOwnership = false; #include "bitVec.h" #include "stlUtil.h" #include "astutil.h" +#include "view.h" // For list_view (debugging). #include "resolution.h" // For AutoDestroyMap. #include @@ -391,7 +397,7 @@ static void processDestructor(SymExpr* se, // All members of an alias clique point to the same SymbolVector, so we only // need to look up one arbitrarily and then run the list. Symbol* sym = se->var; - SymbolVector* aliasList = aliases[sym]; + SymbolVector* aliasList = aliases.at(sym); for_vector(Symbol, alias, *aliasList) { @@ -498,6 +504,34 @@ static void computeTransitions(FnSymbol* fn, } +// In backward flow, we adjust the out set so it is the intersection of the IN +// sets of its successors. If the block is a terminal block (a block with no +// successors), however, we set its OUT to all zeroes. This forces ownership to +// be driven to zero before the function is exited. +// This analysis will work correctly even if the function has multiple exits. +static void backwardFlowOwnership(const BasicBlock::BasicBlockVector& bbs, + FlowSet& IN, FlowSet& OUT) +{ + size_t nbbs = bbs.size(); + for (size_t i = 0; i < nbbs; ++i) + { + if (bbs[i]->outs.size() == 0) + { + // This is a terminal block because it has no successors. + // Its OUT set must be empty. + OUT[i]->clear(); + } + else + { + BitVec* out = OUT[i]; + out->set(); + for_vector(BasicBlock, succ, bbs[i]->outs) + *out &= *IN[succ->id]; + } + } +} + + // This predicate determines if the given statement is a jump. static bool isJump(Expr* stmt) { @@ -517,7 +551,9 @@ static bool isJump(Expr* stmt) // At the end of this basic block, insert an autodestroy for each symbol // specified by the given bit-vector. static void insertAutoDestroy(BasicBlock& bb, BitVec* to_kill, - const SymbolVector& symbols) + const SymbolVector& symbols, + const SymbolIndexMap& symbolIndex, + const AliasVectorMap& aliases) { // Skip degenerate basic blocks. if (bb.exprs.size() == 0) @@ -529,24 +565,52 @@ static void insertAutoDestroy(BasicBlock& bb, BitVec* to_kill, bool isjump = isJump(stmt); // For each true bit in the bit vector, add an autodestroy call. + // But destroying one member of an alias list destroys them all. + SET_LINENO(stmt); for (size_t j = 0; j < to_kill->size(); ++j) { if (to_kill->get(j)) { Symbol* sym = symbols[j]; - CallExpr* autoDestroyCall = new CallExpr(autoDestroyMap.get(sym->type), sym); + + // Remove this symbol and all its aliases from the kill set. + SymbolVector* aliasList = aliases.at(sym); + for_vector(Symbol, alias, *aliasList) + { + size_t index = symbolIndex.at(alias); + to_kill->reset(index); + } + + FnSymbol* autoDestroy = toFnSymbol(autoDestroyMap.get(sym->type)); + if (autoDestroy == NULL) + // This type does not have a destructor, so we don't have a add an + // autoDestroy call for it. + continue; + + // For now, we ignore internally reference-counted types. We'll add the + // code to manage those later. + if (isRefCountedType(sym->type)) + continue; + + CallExpr* autoDestroyCall = new CallExpr(autoDestroy, sym); + if (isjump) stmt->insertBefore(autoDestroyCall); else stmt->insertAfter(autoDestroyCall); + + insertReferenceTemps(autoDestroyCall); } } } + static void insertAutoDestroy(FnSymbol* fn, FlowSet& GEN, FlowSet& KILL, FlowSet& IN, FlowSet& OUT, - const SymbolVector& symbols) + const SymbolVector& symbols, + const SymbolIndexMap& symbolIndex, + const AliasVectorMap& aliases) { size_t nbbs = fn->basicBlocks->size(); for (size_t i = 0; i < nbbs; i++) @@ -555,11 +619,13 @@ static void insertAutoDestroy(FnSymbol* fn, // (live) at the end of the block but is unowned (dead) in the OUT set. BasicBlock& bb = *(*fn->basicBlocks)[i]; BitVec* to_kill = *IN[i] + *GEN[i] - *KILL[i] - *OUT[i]; - insertAutoDestroy(bb, to_kill, symbols); + insertAutoDestroy(bb, to_kill, symbols, symbolIndex, aliases); delete to_kill; to_kill = 0; } } + +// Insert autoDestroy calls in a function as needed. static void insertAutoDestroy(FnSymbol* fn) { BasicBlock::buildBasicBlocks(fn); @@ -594,9 +660,19 @@ static void insertAutoDestroy(FnSymbol* fn) computeTransitions(fn, GEN, KILL, aliases, symbolIndex); + // We can reuse the existing forward flow analysis to compute how ownership + // flows through the basic blocks. BasicBlock::forwardFlowAnalysis(fn, GEN, KILL, IN, OUT, true); - insertAutoDestroy(fn, GEN, KILL, IN, OUT, symbols); + // We need our own equation for backward flow. + // Backward flow determines where ownership must be given up through a + // delete, by making the OUT set the AND of all its successor INs and the IN + // be no greater than OUT | KILL (that is, every symbol owned at the + // beginning of the block (IN) must either appear in OUT or be killed in the + // block. + backwardFlowOwnership(*fn->basicBlocks, IN, OUT); + + insertAutoDestroy(fn, GEN, KILL, IN, OUT, symbols, symbolIndex, aliases); destroyAliasVectorMap(aliases); From 20b76c575e3ff8e8ebbf652dc199cff570111a8e Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Thu, 1 Jan 2015 15:11:23 -0600 Subject: [PATCH 36/53] Add autoCopy on const temps; mark return of PRIM_DEREF as unowned. Down to 1475 regressions. --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 10 ++++++++++ compiler/passes/normalize.cpp | 5 ++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index 8a360fec7b39..279f45b69dda 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -278,6 +278,16 @@ static bool isConstructor(CallExpr* call) if (AggregateType* at = toAggregateType(call->typeInfo())) if (at->isClass()) return false; + + switch (call->primitive->tag) + { + default: + break; // Fall through and return true. + case PRIM_DEREF: + // Because the operand is a reference, its contents are "unowned" by the + // result. An autoCopy needs to be inserted where an owned copy is required. + return false; + } } return true; diff --git a/compiler/passes/normalize.cpp b/compiler/passes/normalize.cpp index 74b76f4d0254..a1ecbcaa01b7 100644 --- a/compiler/passes/normalize.cpp +++ b/compiler/passes/normalize.cpp @@ -915,7 +915,10 @@ fix_def_expr(VarSymbol* var) { if (var->hasFlag(FLAG_CONST) && !var->hasEitherFlag(FLAG_EXTERN, FLAG_REF_VAR)) { constTemp = newTemp("const_tmp"); stmt->insertBefore(new DefExpr(constTemp)); - stmt->insertAfter(new CallExpr(PRIM_MOVE, var, constTemp)); + // Temporary hack: Add an autoCopy here, because the var is being + // initialized with the RHS value. + // Later, this insertion will be handled parsimoniously by insertAutoCopyAutoDestroy. + stmt->insertAfter(new CallExpr(PRIM_MOVE, var, new CallExpr("chpl__autoCopy", constTemp))); } // From ce37533c7e83bec3fc8b98802a6f7ac574fa52d3 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Mon, 5 Jan 2015 17:32:37 -0600 Subject: [PATCH 37/53] Add insertion of autoCopy calls. Rewrite header comments for improved accuracy. Factor out AliasVectorMap manipulations into a class. Factor out common code between gen/kill set creation and autoCopy insertion. Move predicates up toward top of file. Renamed predicates to better indicate how they relate to ownership rather than toward code structure. Moved autoCopy insertion pass up, right after forward flow analysis. [Tested with hello.chpl] --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 746 ++++++++++++------ 1 file changed, 492 insertions(+), 254 deletions(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index 279f45b69dda..397a1484d592 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -26,11 +26,11 @@ static int debug = 0; // insertAutoCopyAutoDestroy // -// Inserts copy-constructor and destructor calls as needed, by tracking the -// "ownership" of record objects. -// //############################################################################# //# +//# Inserts copy-constructor and destructor calls as needed, by tracking the +//# "ownership" of record objects. +//# //# Ownership represents the "liveness" of a record object in the sense that it //# may contain heap-allocated data. After a record object is constructed, any //# of its fields of class type may refer to a class object. In that state, any @@ -40,9 +40,6 @@ static int debug = 0; //# the destructor is called, the record variable no longer owns any //# heap-allocated data, so that data is "unowned". //# -//# In the simplest view, ownership is a binary state: construction changes the -//# state from false to true; destruction changes the state from true to false. -//# //# Ownership of a record can also be transferred by a bit-wise copy. When a //# bit-wise copy of a record is made, class fields (which are implemented as //# pointers) are copied verbatim. That means that both record objects point to @@ -65,9 +62,10 @@ static int debug = 0; //# We assume that the input AST contains the minimum number of //# copy-constructor calls required to provide the specified semantics. That //# is, record values passed by in intent must be copied. We do not yet -//# attempt to pass ownership of an object into a called routine. Two kinds of -//# variable whose ownersihp must be true before exiting a routine are: +//# attempt to pass ownership of an object into a called routine. +//# Variables whose ownersihp must be true before exiting a routine are: //# - The return value variable +//# - A module-level variable. //# - A record field in a record. //# If ownership of any of these is false when they (or their container) is //# about to go out of scope, a copy must be made to bring ownership to the @@ -77,54 +75,75 @@ static int debug = 0; //# After copy-constructor calls have been added, the routine has its full //# complement of construcotr calls. Correct AMM is then just a matter of //# inserting the minimum number of destructor calls to drive the ownership of -//# all local variables to false before the routine ends. Dataflow analysis is -//# used to propagate this constraint backwards, in case ownership is -//# transferred out of a block along one path but not another. +//# all local variables to false before the routine ends. //# //############################################################################# +// This pass is applied to each function in turn. The algorithm has four main +// steps: +// 1. Gather local (argument and variable) symbols and build a vector and index +// of these. +// 2. Traverse each block to populate the GEN and KILL sets and the alias map. +// 3. Perform forward flow analysis to determine which symbols are owned at the +// beginning of each block. Traverse each block again and add autoCopy +// calls so symbols passed to receptor arguments are always owned. +// 4. Perform backward flow analysis to determine which symbols must be unowned +// by the end of each block. Add autoDestroy calls at the end of each block +// for symbols that are owned at the end of the block but must be unowned +// before the block is exited. +// +// Dataflow analysis is used to propagate this constraint backwards, in case +// ownership is transferred out of a block along one path but not another. // The sets used for flow analysis are: -// GEN -- The set of symbols whose ownership transitions to true by the end of +// GEN -- The set of symbols whose ownership transitions to true by the end of +// the block. +// KILL -- The set of symbols whose ownership transitions to false anywhere in // the block. -// KILL -- The set of symbols whose ownership transitions to false by the end -// of the block. -// IN -- The set of symbols that are owned at the beginning of the block. -// OUT -- The set of symbols that are owned at the end of the block. +// IN -- The set of symbols that are owned at the beginning of the block +// (after forward flow analysis). +// OUT -- The set of symbols that are owned at the end of the block (after +// forward flow analysis); the set of symbols that must not be owned +// at the end of the block (after backward flow analysis). // // GEN[i] contains a true bit corresponding to each symbol that is constructed // in that block. We expect that symbol is constructed only once, so if IN[i] // and GEN[i] have a true bit in the same position, we can flag an internal // error. This condition is unlikely, so at best it should be added as a -// verify check. +// verify check. After forward flow analysis, GEN[i] is initialized to IN[i] +// to show that ownership has reached this block from another // -// KILL[i] contains a true bit corresponding to each symbol that is destroyed -// or whose ownership is usurped in that block. We keep a separate list of -// aliases. When a bitwise copy is made, we treat the new copy as if it has -// been constructed without changing the state of the original. When any -// member of that set of aliases is destroyed or usurped, all members of the -// set are added to KILL[i]. +// KILL[i] contains a true bit corresponding to each symbol whose ownership is +// consumed in that block. We keep a separate list of aliases. When any +// member of that set of aliases is consumed, all members of the set are added +// to KILL[i]. // -// We start by performing forward flow analysis to establish the value of IN. +// We start by performing forward flow analysis to establish the value of IN. +// Within each block, OUT[i] <- IN[i] - KILL[i] + GEN[i]. // IN[i] is the intersection of OUT[pred(i)] for all predecessors of block i. -// It is desirable to allow ownership to flow through to the end of the -// function (to a final block with many predecessors). That way, we can -// consume liveness with a minimum of destructor calls. However, if we cannot -// depend on a given symbol being owned at the beginning of a block, then its -// destructor must be pushed back up the flow graph. For example, we might -// pass ownership to a field or global variable on one path; then we need a -// destructor call to get rid of ownership on the other path(s). // -// For a block that contains a constructor followed by a destructor, both -// GEN[i] and KILL[i] will be zero. If they are both true, that represents a -// symbol being destroyed and then reused, so GEN takes precedence over KILL -// in forward flow: OUT[i] = IN[i] - KILL[i] + GEN[i]. +// For a block that contains a constructor followed by a destructor for the +// same symbol j, GEN[i,j] will be false and KILL[i,j] will be true. +// For a block that contains a destructor followed by a constructor for the +// same symbol j, GEN[i,j] and KILL[i,j] will both be true. // // For backward flow, we compute OUT[i] as the intersection of IN[succ(i)] for // all successors of block i. (No information is propagated backward through -// the blocks, so this can be done in one iteration.) Then, in the local -// traversal the follows, for any symbol that remains owned at the end of block -// i where its OUT[i] is false, we add a destructor call so that the results of -// forward and backward flow agree. +// the blocks, so this can be done in one iteration.) +// +// After forward flow analysis, we traverse blocks again and add autoCopy calls +// where a symexpr is unowned (at that point in the flow) but being passed to +// an argument that consumes it. (Consumers expect the corresonding operand to +// be owned; the autoCopy call adds the required ownership.) Note that adding +// an autoCopy to an operand does not change GEN[i] or KILL[i], so the results +// of forward flow do not have to be recomputed after this pass. +// +// For any symbol that remains owned at the end of block i where its OUT[i] is +// false, we add a destructor call so that the results of forward and backward +// flow agree. Each added destructor call effectively sets the corresponding +// KILL[i,j] and clears the corresponding GEN[i,j]. If we update these flow +// sets during this pass, then we could check IN[i] - KILL[i] + GEN[i] == +// OUT[i]. But since we are iterating on the difference between these two +// bitsets (i.e. OUT - (IN - KILL + GEN)), the test becomes a tautology. // #include "passes.h" // The main routine is declared here. @@ -145,8 +164,6 @@ typedef std::vector FlowSet; typedef std::vector SymbolVector; typedef std::map SymbolIndexMap; typedef SymbolIndexMap::value_type SymbolIndexElement; -typedef std::map AliasVectorMap; -typedef AliasVectorMap::value_type AliasMapElement; typedef std::vector SymExprVector; typedef std::vector DefExprVector; @@ -164,33 +181,106 @@ static void createFlowSet(std::vector& set, static void destroyFlowSet(std::vector set) { for_vector(BitVec, vec, set) - delete vec, vec = 0; + delete vec; vec = 0; } -// An alias vector map is map from symbols to lists of symbols, but all of the -// symbols in the same clique share the same list. So, to destroy all the -// lists in the map, we find the list through the first member of the clique, -// then set the map element for each member in the list to NULL. (The list -// contains the element though which the list is accessed, so this detaches the -// list from all map elements.) Then we delete the list. (The symbols -// contained in the list are owned elsewhere.) -static void destroyAliasVectorMap(AliasVectorMap& aliases) +//############################## AliasVectorMap ############################## +//# +//# A utility class derived from std::map. +//# An element in this map can contain dynamically-allocated memory through its +//# SymbolVector (second) field. So the main point of the class is to provide +//# a destructor that will clean up an instance properly. +//# The insert and merge utilities manipulate the data structure while +//# maintaining its main invariants: +//# 1. Each member maps a symbol to a vector of symbols. +//# 2. The vector of symbols contains at least the same symbol and also other +//# symbols with which it is an alias. +//# 3. The set of mutual aliases forms a clique. Since this set is common to +//# all its members, the symbol vector representing a clique is shared among all +//# members belonging to that clique. +//# +//######################################################################## + +class AliasVectorMap : public std::map +{ + public: + typedef value_type AliasMapElement; + + private: + // Copying an alias vector map involves copying the symbol vector -- which we + // don't want to do, so we just disallow copying instead. + AliasVectorMap(const AliasVectorMap& rhs); + void operator=(const AliasVectorMap& rhs); + + public: + ~AliasVectorMap(); + AliasVectorMap() : std::map() {} + + void insert(Symbol* sym); + void merge(Symbol* orig, Symbol* alias); +}; + +// To destroy all the lists in the map, we find the list through the first +// member of the clique, then set the map element for each member in the list +// to NULL. Then we delete the list. +AliasVectorMap::~AliasVectorMap() { - for (AliasVectorMap::iterator it = aliases.begin(); - it != aliases.end(); ++it) + for (iterator it = begin(); + it != end(); ++it) { if (it->second == NULL) + // This entry is part of a clique that was already deleted. continue; SymbolVector* aliasList = it->second; for_vector(Symbol, sym, *aliasList) - aliases[sym] = NULL; + at(sym) = NULL; delete aliasList; } } +// Insert a symbol into the alias vector map. +// When a symbol is first inserted it is associated with an alias vector that +// contains only itself. +void +AliasVectorMap::insert(Symbol* sym) +{ + SymbolVector* aliasList = new SymbolVector(); + aliasList->push_back(sym); + operator[](sym) = aliasList; +} + +// Merge the alias lists of two symbols that have become aliases. +void +AliasVectorMap::merge(Symbol* orig, Symbol* alias) +{ + SymbolVector* origList = at(orig); + SymbolVector* aliasList = at(alias); + + if (aliasList == origList) + { + // These two symbols are already aliases. + return; + } + + // Copy elements from the alias's list into the original symbol's list and + // set each entry in the map corresponding to those elements to point to the + // updated original list. + for (size_t i = 0; i < aliasList->size(); ++i) + { + Symbol* sym = aliasList->at(i); + origList->push_back(sym); + operator[](sym) = origList; + } + + // After that, no elements point to this aliasList, so it is deleted. + delete aliasList; +} + +//######################### End of AliasVectorMap ######################### + // Scans the body of the given function and inserts all of the variable and // argument symbols defined in it into the vector of symbols. Bits in the flow @@ -198,8 +288,6 @@ static void destroyAliasVectorMap(AliasVectorMap& aliases) // Also constructs an index map, to make it easier to find the index of a // symbol in the vector. (Otherwise, a linear search is required.) // The alias map can also be populated at the same time. -// TODO: Split the alias list off as a separate class, mostly to make it easy -// to document the data structure it implements. static void extractSymbols(FnSymbol* fn, SymbolVector& symbols, @@ -251,206 +339,264 @@ extractSymbols(FnSymbol* fn, INT_ASSERT(symbols[symbolIndex[sym]] == sym); // Initialize each entry in the alias map with a list of symbols - // containingh the symbol itself. - SymbolVector* aliasList = new SymbolVector(); - aliasList->push_back(sym); - aliases.insert(AliasMapElement(sym, aliasList)); + // containing just the symbol itself. + aliases.insert(sym); } } -// Returns true if this function returns a fully-constructed value; false otherwise. -static bool isConstructor(CallExpr* call) -{ - if (FnSymbol* fn = call->isResolved()) - { - // A "normal" function. +//############################## Predicates ############################## +//# - // Return values of class type are ruled out. - Type* retType = fn->retType; - if (AggregateType* at = toAggregateType(retType)) - if (at->isClass()) - return false; - } - else +// Returns true if the given SymExpr is contructed by the expression in which it +// appears; false otherwise. +static bool isCreated(SymExpr* se) +{ + if (CallExpr* call = toCallExpr(se->parentExpr)) { - // A primitive. - if (AggregateType* at = toAggregateType(call->typeInfo())) - if (at->isClass()) + // In the current incarnation, we expect construction to look like: + // ('move' lhs (construct )) + // When constructors turn into methods, this will look a bit different. + if (call->isPrimitive(PRIM_MOVE) || + call->isPrimitive(PRIM_ASSIGN)) + { + // We expect the target symbol to appear as the first operand. + if (call->get(1) != se) return false; - switch (call->primitive->tag) - { - default: - break; // Fall through and return true. - case PRIM_DEREF: - // Because the operand is a reference, its contents are "unowned" by the - // result. An autoCopy needs to be inserted where an owned copy is required. - return false; + if (CallExpr* rhsCall = toCallExpr(call->get(2))) + { + if (FnSymbol* fn = rhsCall->isResolved()) + { + // A "normal" function. + + // Return values of class type are ruled out. + Type* retType = fn->retType; + if (AggregateType* at = toAggregateType(retType)) + if (at->isClass()) + return false; + } + else + { + // A primitive. + + // We are only interested in record types, so if the RHS is not of + // aggregate type, we can skip it. + if (AggregateType* at = toAggregateType(rhsCall->typeInfo())) + if (at->isClass()) + return false; + + switch (rhsCall->primitive->tag) + { + default: + break; // Fall through and return true. + case PRIM_DEREF: + // Because the operand is a reference, its contents are "unowned" by the + // result. An autoCopy needs to be inserted where an owned copy is required. + return false; + } + } + return true; + } } } - return true; + // Return false by default. + return false; } -static void processConstructor(CallExpr* call, SymExpr* se, - BitVec* gen, - const SymbolIndexMap& symbolIndex) +// Returns true if the expression in which the given SymExpr appears makes it a +// bitwise copy of some other symbol; false otherwise. +static bool isBitwiseCopy(SymExpr* se) { - // In the current incarnation, we expect construction to look like: - // ('move' lhs (construct )) - // When constructors turn into methods, this will look a bit different. - if (call->isPrimitive(PRIM_MOVE) || - call->isPrimitive(PRIM_ASSIGN)) + // Must be a call (as opposed to a DefExpr or LabelExpr, etc. + if (CallExpr* call = toCallExpr(se->parentExpr)) { - if (CallExpr* rhsCall = toCallExpr(call->get(2))) + // Call must be a move or assignment + if (call->isPrimitive(PRIM_MOVE) || + call->isPrimitive(PRIM_ASSIGN)) { - if (isConstructor(rhsCall)) + // se must be the first argument. + if (call->get(1) == se) { - // Any function returning a value is considered to be a constructor. - Symbol* sym = se->var; - size_t index = symbolIndex.at(sym); - // We expect that each symbol gets constructed only once, so if we are - // about to set a bit in the gen set, it cannot already be true. - if (gen->get(index)) - if (fWarnOwnership) - USR_WARN(sym, "Reinitialization of sym"); - // If this assumption turns out to be false, it means we are reusing - // symbols. That case can be accommodated, but it means we have to - // insert a destructor call ahead of the symbol's reinitialization. - gen->set(index); + // The second argument must also be a symexpr. + if (isSymExpr(call->get(2))) + return true; } } } + return false; } -// Merge the alias lists of two symbols that have become aliases. -static void mergeAliases(Symbol* orig, Symbol* alias, - AliasVectorMap& aliases) +// Returns true if the expression in which the given SymExpr appears causes +// its ownership to be consumed; false otherwise. +static bool isConsumed(SymExpr* se) { - SymbolVector* origList = aliases[orig]; - SymbolVector* aliasList = aliases[alias]; - - if (aliasList == origList) + if (CallExpr* call = toCallExpr(se->parentExpr)) { - // These two symbols are already aliases. - return; - } - - for (size_t i = 0; i < aliasList->size(); ++i) - { - Symbol* sym = aliasList->at(i); - origList->push_back(sym); - aliases[sym] = origList; - } - delete aliasList; -} - + if (FnSymbol* fn = call->isResolved()) + { + // This is a function call. -static void processMove(CallExpr* call, SymExpr* se, BitVec* gen, AliasVectorMap& aliases, - const SymbolIndexMap& symbolIndex) -{ - // We only care about bitwise copies here. - if (call->isPrimitive(PRIM_MOVE) || - call->isPrimitive(PRIM_ASSIGN)) - { - // We'll key off the LHS. - // Meaning that we will only pay attention to the expression if the SymExpr - // is in the LHS position in the primitive. - if (SymExpr* lhs = toSymExpr(call->get(1))) + // The only one we're interested in right now is a destructor call. + if (fn->hasFlag(FLAG_DESTRUCTOR)) + { + // Paranoid check: This SymExpr is the thing being destroyed, right? + INT_ASSERT(call->get(1) == se); + return true; + } + } + else { - if (lhs == se) + // This is a primitive. + if (call->isPrimitive(PRIM_RETURN)) + { + // Returns act like destructors. + return true; + } +#if 0 + // Not yet. + if (call->isPrimitive(PRIM_MOVE)) { - // We only care about bitwise copies from one symbol to another. - if (SymExpr* rhs = toSymExpr(call->get(2))) + // If the left side of a move is a global, it assumes ownership from the + // RHS. We can assume that the RHS is local. + Expr* lhs = call->get(1); + if (SymExpr* lhse = toSymExpr(lhs)) { - Symbol* lsym = lhs->var; - Symbol* rsym = rhs->var; - size_t lindex = symbolIndex.at(lsym); - SymbolIndexMap::const_iterator rsymPair = symbolIndex.find(rsym); - if (rsymPair == symbolIndex.end()) - { - // TODO: A move from a global or extern symbol without a copy-constructor - // call is probably an AST consistency or codegen error, given that - // we have already weeded out fundamental, extern and class types. - return; - } - size_t rindex = rsymPair->second; - - // Copy ownership state from RHS. - INT_ASSERT(gen->get(lindex) == false); - if (!gen->get(rindex)) - { - if (fWarnOwnership) - USR_WARN(rsym, "Uninitialized symbol is copied here"); - } - else - { - gen->set(lindex); - } - // Merge aliases whether or not they are live. - mergeAliases(rsym, lsym, aliases); + if (lhse->var->type->symbol->hasFlag(FLAG_REF) || + lhse->var->defPoint->parentSymbol->hasFlag(FLAG_MODULE_INIT)) + return true; } } +#endif } } + + return false; } -static void processDestructor(SymExpr* se, - BitVec* kill, AliasVectorMap& aliases, - const SymbolIndexMap& symbolIndex) +// This predicate determines if the given statement is a jump. +static bool isJump(Expr* stmt) { - // Add all members of an alias clique to the kill set. - // All members of an alias clique point to the same SymbolVector, so we only - // need to look up one arbitrarily and then run the list. + // A goto is definitely a jump. + if (isGotoStmt(stmt)) + return true; + + // A return primitive works like a jump. (Nothing should appear after it.) + if (CallExpr* call = toCallExpr(stmt)) + if (call->isPrimitive(PRIM_RETURN)) + return true; + + return false; +} + + +static void processCreator(SymExpr* se, + BitVec* gen, + const SymbolIndexMap& symbolIndex) +{ + // Any function returning a value is considered to be a constructor. Symbol* sym = se->var; - SymbolVector* aliasList = aliases.at(sym); + size_t index = symbolIndex.at(sym); + + // We expect that each symbol gets constructed only once, so if we are + // about to set a bit in the gen set, it cannot already be true. + // If this assumption turns out to be false, it means we are reusing + // symbols. That case can be accommodated, but it means we should + // insert a destructor call ahead of the symbol's reinitialization. + if (gen->get(index)) + if (fWarnOwnership) + USR_WARN(sym, "Reinitialization of sym"); + + gen->set(index); +} - for_vector(Symbol, alias, *aliasList) + +static void processBitwiseCopy(SymExpr* se, BitVec* gen, + const SymbolIndexMap& symbolIndex) +{ + CallExpr* call = toCallExpr(se->parentExpr); + + SymExpr* lhs = toSymExpr(call->get(1)); + SymExpr* rhs = toSymExpr(call->get(2)); + + Symbol* lsym = lhs->var; + Symbol* rsym = rhs->var; + + size_t lindex = symbolIndex.at(lsym); + SymbolIndexMap::const_iterator rsymPair = symbolIndex.find(rsym); + if (rsymPair == symbolIndex.end()) { - size_t index = symbolIndex.at(alias); - // We expect a symbol to be live when it is killed. - INT_ASSERT(kill->get(index) == false); - kill->set(index); + // TODO: A move from a global or extern symbol without a copy-constructor + // call is probably an AST consistency or codegen error, given that + // we have already weeded out fundamental, extern and class types. + return; } + size_t rindex = rsymPair->second; - // We don't bother updating the alias list under the assumption that - // symbols are not reused. If that turns out to be false, we have to - // remove the alias clique from aliases here. + // Copy ownership state from RHS. + INT_ASSERT(gen->get(lindex) == false); + if (!gen->get(rindex)) + { + if (fWarnOwnership) + USR_WARN(rsym, "Uninitialized symbol is copied here"); + // This can be a problem because the way we track aliases assumes that the + // ownership state of every member of the alias clique is the same. + // If this assumption turns out to be untrue, then we have to track aliases + // statement by statement, which will be more expensive. + // This assumption would be violated if for example we have: + // var a + // var b + // (move b a) + // (move a _defaultOf(type_tmp)) + // Now b is an alias of a (they way we currently track aliases), but the + // value of a is owned while the value of b is not. + } + else + { + gen->set(lindex); + } +} + + +static void createAlias(SymExpr* se, AliasVectorMap& aliases) +{ + CallExpr* call = toCallExpr(se->parentExpr); + + SymExpr* lhs = toSymExpr(call->get(1)); + SymExpr* rhs = toSymExpr(call->get(2)); + + Symbol* lsym = lhs->var; + Symbol* rsym = rhs->var; + + // Merge aliases whether or not they are live. + aliases.merge(rsym, lsym); } // If this call acts like a destructor, then add the symbols it affects to the -// kill set. -static void processDestructor(CallExpr* call, SymExpr* se, BitVec* kill, - AliasVectorMap& aliases, - const SymbolIndexMap& symbolIndex) +// kill set and remove them from the gen set. +static void processConsumer(SymExpr* se, BitVec* gen, BitVec* kill, + const AliasVectorMap& aliases, + const SymbolIndexMap& symbolIndex) { - if (FnSymbol* fn = call->isResolved()) - { - // This is a function call. + // Add all members of an alias clique to the kill set and remove them from + // the gen set. - // The only one we're interested in right now is a destructor call. - if (fn->hasFlag(FLAG_DESTRUCTOR)) - { - // Paranoid check: This SymExpr is the thing being destroyed, right? - INT_ASSERT(call->get(1) == se); + // All members of an alias clique point to the same SymbolVector, so we only + // need to look up one arbitrarily and then run the list. + Symbol* sym = se->var; + SymbolVector* aliasList = aliases.at(sym); - processDestructor(se, kill, aliases, symbolIndex); - } - } - else + for_vector(Symbol, alias, *aliasList) { - // This is a primitive. - if (call->isPrimitive(PRIM_RETURN)) - { - // Returns act like destructors. - processDestructor(se, kill, aliases, symbolIndex); - } - } + size_t index = symbolIndex.at(alias); + gen->reset(index); + kill->set(index); + } } @@ -467,13 +613,17 @@ static void computeTransitions(SymExprVector& symExprs, if (symbolIndex.find(sym) == symbolIndex.end()) continue; - // We are only interested in call expressions involving the SymExpr. - if (CallExpr* call = toCallExpr(se->parentExpr)) + if (isCreated(se)) + processCreator(se, gen, symbolIndex); + + if (isBitwiseCopy(se)) { - processConstructor(call, se, gen, symbolIndex); - processMove(call, se, gen, aliases, symbolIndex); - processDestructor(call, se, kill, aliases, symbolIndex); + processBitwiseCopy(se, gen, symbolIndex); + createAlias(se, aliases); } + + if (isConsumed(se)) + processConsumer(se, gen, kill, aliases, symbolIndex); } } @@ -514,47 +664,117 @@ static void computeTransitions(FnSymbol* fn, } -// In backward flow, we adjust the out set so it is the intersection of the IN -// sets of its successors. If the block is a terminal block (a block with no -// successors), however, we set its OUT to all zeroes. This forces ownership to -// be driven to zero before the function is exited. -// This analysis will work correctly even if the function has multiple exits. -static void backwardFlowOwnership(const BasicBlock::BasicBlockVector& bbs, - FlowSet& IN, FlowSet& OUT) +static bool isRetVarInReturn(SymExpr* se) { - size_t nbbs = bbs.size(); - for (size_t i = 0; i < nbbs; ++i) + if (CallExpr* call = toCallExpr(se->parentExpr)) + if (call->isPrimitive(PRIM_RETURN)) + // We just assume that that call->get(1) == se. + // What else could it be? + return true; + + return false; +} + + +// Insert an autoCopy because this symbol is unowned and +// a receptor formal expects is operand to be owned. +static void insertAutoCopy(SymExpr* se) +{ + SET_LINENO(se); + Symbol* sym = se->var; + FnSymbol* autoCopyFn = autoCopyMap.get(sym->type); + + if (autoCopyFn == NULL) + return; + + // For now, we ignore internally reference-counted types. We'll add the + // code to manage those later. + if (isRefCountedType(sym->type)) + return; + + // Prevent autoCopy functions from calling themselves recursively. + // TODO: Remove this clause after the autoCopy function becomes a copy constructor + // method. + if (isRetVarInReturn(se)) + return; + + CallExpr* autoCopyCall = new CallExpr(autoCopyFn, se->copy()); + se->replace(autoCopyCall); +} + + +static void insertAutoCopy(SymExprVector& symExprs, BitVec* gen, BitVec* kill, + const AliasVectorMap& aliases, + const SymbolIndexMap& symbolIndex) +{ + for_vector(SymExpr, se, symExprs) { - if (bbs[i]->outs.size() == 0) - { - // This is a terminal block because it has no successors. - // Its OUT set must be empty. - OUT[i]->clear(); - } - else + // We are only interested in local symbols, so if this one does not appear + // in our map, move on. + Symbol* sym = se->var; + if (symbolIndex.find(sym) == symbolIndex.end()) + continue; + + // Set a bit in the gen set if this is a constructor. + if (isCreated(se)) + processCreator(se, gen, symbolIndex); + + // Pass ownership to this symbol if it is the result of a bitwise copy. + if (isBitwiseCopy(se)) + processBitwiseCopy(se, gen, symbolIndex); + + if (isConsumed(se)) { - BitVec* out = OUT[i]; - out->set(); - for_vector(BasicBlock, succ, bbs[i]->outs) - *out &= *IN[succ->id]; + // If the gen bit is set for this symbol, we can leave it as a move and + // transfer ownership. Otherwise, we need to insert an autoCopy. + size_t index = symbolIndex.at(sym); + if (!gen->get(index)) + insertAutoCopy(se); + + processConsumer(se, gen, kill, aliases, symbolIndex); } } } -// This predicate determines if the given statement is a jump. -static bool isJump(Expr* stmt) +static void insertAutoCopy(BasicBlock& bb, + BitVec* gen, BitVec* kill, + const SymbolVector& symbols, + const SymbolIndexMap& symbolIndex, + const AliasVectorMap& aliases) { - // A goto is definitely a jump. - if (isGotoStmt(stmt)) - return true; + for_vector(Expr, expr, bb.exprs) + { + SymExprVector symExprs; + collectSymExprsSTL(expr, symExprs); - // A return primitive works like a jump. (Nothing should appear after it.) - if (CallExpr* call = toCallExpr(stmt)) - if (call->isPrimitive(PRIM_RETURN)) - return true; + insertAutoCopy(symExprs, gen, kill, aliases, symbolIndex); + } +} - return false; + +static void insertAutoCopy(FnSymbol* fn, + FlowSet& GEN, FlowSet& KILL, + FlowSet& IN, FlowSet& OUT, + const SymbolVector& symbols, + const SymbolIndexMap& symbolIndex, + const AliasVectorMap& aliases) +{ + size_t nbbs = fn->basicBlocks->size(); + for (size_t i = 0; i < nbbs; i++) + { + // We need to insert an autodestroy call for each symbol that is owned + // (live) at the end of the block but is unowned (dead) in the OUT set. + BasicBlock& bb = *(*fn->basicBlocks)[i]; + // For this block traversal, we initialize GEN[i] = IN[i]. + // We don't care about the local kill, so we just use a new one. + // (We could compare new with old as a sanity check following the traversal.) + BitVec* local_gen = new BitVec(*IN[i]); + BitVec* local_kill = new BitVec(KILL[i]->size()); + insertAutoCopy(bb, local_gen, local_kill, symbols, symbolIndex, aliases); + delete local_kill; local_kill = 0; + delete local_gen; local_gen = 0; + } } @@ -635,8 +855,36 @@ static void insertAutoDestroy(FnSymbol* fn, } +// In backward flow, we adjust the out set so it is the intersection of the IN +// sets of its successors. If the block is a terminal block (a block with no +// successors), however, we set its OUT to all zeroes. This forces ownership to +// be driven to zero before the function is exited. +// This analysis will work correctly even if the function has multiple exits. +static void backwardFlowOwnership(const BasicBlock::BasicBlockVector& bbs, + FlowSet& IN, FlowSet& OUT) +{ + size_t nbbs = bbs.size(); + for (size_t i = 0; i < nbbs; ++i) + { + if (bbs[i]->outs.size() == 0) + { + // This is a terminal block because it has no successors. + // Its OUT set must be empty. + OUT[i]->clear(); + } + else + { + BitVec* out = OUT[i]; + out->set(); + for_vector(BasicBlock, succ, bbs[i]->outs) + *out &= *IN[succ->id]; + } + } +} + + // Insert autoDestroy calls in a function as needed. -static void insertAutoDestroy(FnSymbol* fn) +static void insertAutoCopyAutoDestroy(FnSymbol* fn) { BasicBlock::buildBasicBlocks(fn); size_t nbbs = fn->basicBlocks->size(); @@ -674,6 +922,8 @@ static void insertAutoDestroy(FnSymbol* fn) // flows through the basic blocks. BasicBlock::forwardFlowAnalysis(fn, GEN, KILL, IN, OUT, true); + insertAutoCopy(fn, GEN, KILL, IN, OUT, symbols, symbolIndex, aliases); + // We need our own equation for backward flow. // Backward flow determines where ownership must be given up through a // delete, by making the OUT set the AND of all its successor INs and the IN @@ -684,8 +934,6 @@ static void insertAutoDestroy(FnSymbol* fn) insertAutoDestroy(fn, GEN, KILL, IN, OUT, symbols, symbolIndex, aliases); - destroyAliasVectorMap(aliases); - destroyFlowSet(GEN); destroyFlowSet(KILL); destroyFlowSet(IN); @@ -693,7 +941,7 @@ static void insertAutoDestroy(FnSymbol* fn) } -static void insertAutoDestroy() +void insertAutoCopyAutoDestroy() { forv_Vec(FnSymbol, fn, gFnSymbols) { @@ -701,16 +949,6 @@ static void insertAutoDestroy() if (fn->hasFlag(FLAG_FUNCTION_PROTOTYPE)) continue; - insertAutoDestroy(fn); + insertAutoCopyAutoDestroy(fn); } } - - -void insertAutoCopyAutoDestroy() -{ - // First of all, just insert autoDestroy calls. - // We assume that all necessary autoCopy calls have been added. - // As an enhancement/optimization, we'll add a separate pass to compute and - // add the minimum number of autoCopy calls required. - insertAutoDestroy(); -} From 085fa0a16a98e7c2a8c28044b057aae9255560f2 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Tue, 6 Jan 2015 01:39:53 -0600 Subject: [PATCH 38/53] Update return-record.chpl with initCopy function. --- test/types/records/kbrady/return-record.chpl | 10 ++++++- test/types/records/kbrady/return-record.good | 30 +++++++++++++++++--- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/test/types/records/kbrady/return-record.chpl b/test/types/records/kbrady/return-record.chpl index ddc0e04f35eb..bb3c8421d35b 100644 --- a/test/types/records/kbrady/return-record.chpl +++ b/test/types/records/kbrady/return-record.chpl @@ -14,10 +14,18 @@ module foo { proc ~Zed() { writeln("in ~Zed(",x,")"); - delete this.x; + delete this.x; this.x = nil; } } + pragma "init copy fn" + proc chpl__initCopy(const ref rhs: Zed) { + writeln("in chpl__initCopy(", rhs.x, ":Zed)"); + var result: Zed; + result.x = if rhs.x then new helper(rhs.x.y) else nil; + return result; + } + proc =(ref lhs: Zed, rhs: Zed) { if debugAssign then writeln("in =(Zed(",lhs.x,"), Zed(",rhs.x,"))"); else writeln("in =(Zed, Zed)"); diff --git a/test/types/records/kbrady/return-record.good b/test/types/records/kbrady/return-record.good index 0bcf49f1a19b..a8a925ce49a2 100644 --- a/test/types/records/kbrady/return-record.good +++ b/test/types/records/kbrady/return-record.good @@ -2,10 +2,20 @@ type inferred ------------- in baz() in Zed(1) -in =(Zed, Zed) +in chpl__initCopy({y = 1}:Zed) +in chpl__initCopy({y = 1}:Zed) +in chpl__initCopy({y = 1}:Zed) +in chpl__initCopy({y = 1}:Zed) +in chpl__initCopy({y = 1}:Zed) +in chpl__initCopy({y = 1}:Zed) +(x = {y = 1})in ~Zed({y = 1}) + +in ~Zed({y = 1}) +in ~Zed({y = 1}) +in ~Zed({y = 1}) +in ~Zed({y = 1}) +in ~Zed({y = 1}) in ~Zed({y = 1}) -(x = {y = 2}) -in ~Zed({y = 2}) type specified -------------- @@ -13,5 +23,17 @@ in gob() in Zed(1) in =(Zed, Zed) in ~Zed({y = 1}) -(x = {y = 2}) +in chpl__initCopy({y = 2}:Zed) +in chpl__initCopy({y = 2}:Zed) +in chpl__initCopy({y = 2}:Zed) +in chpl__initCopy({y = 2}:Zed) +in chpl__initCopy({y = 2}:Zed) +in chpl__initCopy({y = 2}:Zed) +(x = {y = 2})in ~Zed({y = 2}) + +in ~Zed({y = 2}) +in ~Zed({y = 2}) +in ~Zed({y = 2}) +in ~Zed({y = 2}) +in ~Zed({y = 2}) in ~Zed({y = 2}) From e1f28e2c44fbb09896e36fc8dbe47a4b00f3bb6b Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Tue, 6 Jan 2015 01:46:46 -0600 Subject: [PATCH 39/53] Added a few more cases to isConsumed(), so that more autoCopy calls are inserted. In isConsumed(), added cases for PRIM_MOVE to a global and PRIM_SET_MEMBER. Use temporaries to store the result of an autocopy call, to keep returnStarTuplesByRefArgs happy. 1361 failures. W00t! --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 44 ++++++++++++------- compiler/passes/returnStarTuplesByRefArgs.cpp | 3 ++ 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index 397a1484d592..a43e9f1f9dcd 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -451,26 +451,35 @@ static bool isConsumed(SymExpr* se) else { // This is a primitive. - if (call->isPrimitive(PRIM_RETURN)) + switch(call->primitive->tag) { + default: + // Assume that, generally speaking, a primitive does not consume its argument. + return false; + + case PRIM_RETURN: // Returns act like destructors. return true; - } -#if 0 - // Not yet. - if (call->isPrimitive(PRIM_MOVE)) - { - // If the left side of a move is a global, it assumes ownership from the - // RHS. We can assume that the RHS is local. - Expr* lhs = call->get(1); - if (SymExpr* lhse = toSymExpr(lhs)) + + case PRIM_MOVE: { - if (lhse->var->type->symbol->hasFlag(FLAG_REF) || - lhse->var->defPoint->parentSymbol->hasFlag(FLAG_MODULE_INIT)) - return true; + // If the left side of a move is a global, it assumes ownership from the + // RHS. We can assume that the RHS is local. + Expr* lhs = call->get(1); + if (SymExpr* lhse = toSymExpr(lhs)) + { + if (//lhse->var->type->symbol->hasFlag(FLAG_REF) || + isModuleSymbol(lhse->var->defPoint->parentSymbol)) + return true; + } } + break; + + case PRIM_SET_MEMBER: + if (call->get(3) == se) + return true; + break; } -#endif } } @@ -698,8 +707,13 @@ static void insertAutoCopy(SymExpr* se) if (isRetVarInReturn(se)) return; + Expr* stmt = se->getStmtExpr(); + VarSymbol* tmp = newTemp("auto_copy_tmp", sym->type); CallExpr* autoCopyCall = new CallExpr(autoCopyFn, se->copy()); - se->replace(autoCopyCall); + stmt->insertBefore(new DefExpr(tmp)); + stmt->insertBefore(new CallExpr(PRIM_MOVE, tmp, autoCopyCall)); + se->replace(new SymExpr(tmp)); + insertReferenceTemps(autoCopyCall); } diff --git a/compiler/passes/returnStarTuplesByRefArgs.cpp b/compiler/passes/returnStarTuplesByRefArgs.cpp index 9a30f0caac36..9aaf8ccf1d11 100644 --- a/compiler/passes/returnStarTuplesByRefArgs.cpp +++ b/compiler/passes/returnStarTuplesByRefArgs.cpp @@ -85,6 +85,9 @@ void returnStarTuplesByRefArgs() { // SET/GET_SVEC_MEMBER primitives // forv_Vec(CallExpr, call, gCallExprs) { + if (! call->inTree()) + continue; + if (call->isPrimitive(PRIM_SET_MEMBER) || call->isPrimitive(PRIM_GET_MEMBER) || call->isPrimitive(PRIM_GET_MEMBER_VALUE)) { From 1c311694033637721d57b11cb8affe62e88ce885 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Tue, 6 Jan 2015 14:05:49 -0600 Subject: [PATCH 40/53] Comment out the clause that prevents insertion of autoCopy calls for reference counted types. Some tests involving internally ref-counted types were failing due to the reference count going negative. This turned out to be due to record-wrapped types (the same set) being inserted into tuples without their copy-constructor (autoCopy) being called. The destructor for a tuple calls autoDestroy on its elements, so these autoCopy calls are required. It is probably the case that destructor calls were omitted for record-wrapped elements in a tuple. But since I'm coding the insertion of destructor calls from scratch and trying to keep the implementation as uniform as possible, tuple destructors contain destructors for all of their record elements, and are called wherever a tuple local needs to be destroyed. Adding more autoCopies exposed that we may have RWT objects that are empty (i.e. contain a nil class object) and autoCopy should be robust to that. 291 failures remaining --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 5 +++++ modules/internal/ChapelBase.chpl | 12 +++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index a43e9f1f9dcd..24ba34b3bae3 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -696,10 +696,15 @@ static void insertAutoCopy(SymExpr* se) if (autoCopyFn == NULL) return; +// This clause is commented out, because when arrays were inserted into tuples, +// their refcounts were not getting bumped, but when the tuple is deleted, the +// array is deleted and that decrements the reference count. +#if 0 // For now, we ignore internally reference-counted types. We'll add the // code to manage those later. if (isRefCountedType(sym->type)) return; +#endif // Prevent autoCopy functions from calling themselves recursively. // TODO: Remove this clause after the autoCopy function becomes a copy constructor diff --git a/modules/internal/ChapelBase.chpl b/modules/internal/ChapelBase.chpl index 8af09e1540b7..0b0a285e5b78 100644 --- a/modules/internal/ChapelBase.chpl +++ b/modules/internal/ChapelBase.chpl @@ -1009,7 +1009,9 @@ module ChapelBase { pragma "removable auto copy" pragma "donor fn" pragma "auto copy fn" proc chpl__autoCopy(x: _distribution) { - if !noRefCount then x._value.incRefCount(); + if !noRefCount then + if x._value != nil then + x._value.incRefCount(); return x; } @@ -1017,7 +1019,9 @@ module ChapelBase { pragma "removable auto copy" pragma "donor fn" pragma "auto copy fn" proc chpl__autoCopy(x: domain) { - if !noRefCount then x._value.incRefCount(); + if !noRefCount then + if x._value != nil then + x._value.incRefCount(); return x; } @@ -1025,7 +1029,9 @@ module ChapelBase { pragma "removable auto copy" pragma "donor fn" pragma "auto copy fn" proc chpl__autoCopy(x: []) { - if !noRefCount then x._value.incRefCount(); + if !noRefCount then + if x._value != nil then + x._value.incRefCount(); return x; } From a78e183f8d3e0b91f2a687cc6b8df1be2b10f4b3 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Tue, 6 Jan 2015 18:28:49 -0600 Subject: [PATCH 41/53] Strengthened the test in isBitwiseCopy(), so it returns false if the RHS is global. Removed the commented-out clause that prevented autoCopy insertion for ref-counted types (no change in functionality). Updated some test output because the copy constructor is called for members of tuples much more often now. In users/ferguson/refcnt, the output changed because autodestroy calls have been moved closer to the end of the routine. This is good, because it shows that in the new implementation there are more opportunities for an owned variable to be consumed before it goes out of scope. --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 38 ++++++++----------- test/classes/diten/record_initialize.good | 25 +++++++++++- .../records/hilde/newUserDefaultCtor.good | 2 +- test/users/ferguson/refcnt.good | 22 +++++------ 4 files changed, 51 insertions(+), 36 deletions(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index 24ba34b3bae3..7b568857e52c 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -408,7 +408,8 @@ static bool isCreated(SymExpr* se) // Returns true if the expression in which the given SymExpr appears makes it a // bitwise copy of some other symbol; false otherwise. -static bool isBitwiseCopy(SymExpr* se) +static bool isBitwiseCopy(SymExpr* se, + const SymbolIndexMap& symbolIndex) { // Must be a call (as opposed to a DefExpr or LabelExpr, etc. if (CallExpr* call = toCallExpr(se->parentExpr)) @@ -421,8 +422,17 @@ static bool isBitwiseCopy(SymExpr* se) if (call->get(1) == se) { // The second argument must also be a symexpr. - if (isSymExpr(call->get(2))) + if (SymExpr* rhse = toSymExpr(call->get(2))) + { + // And it must contain a local symbol. + Symbol* rhs = rhse->var; + if (symbolIndex.find(rhs) == symbolIndex.end()) + // Not found, so skip this move. + // The lhs will be considered unowned, which is correct because it + // is just a bitwise copy of the value of the global. + return false; return true; + } } } } @@ -536,15 +546,7 @@ static void processBitwiseCopy(SymExpr* se, BitVec* gen, Symbol* rsym = rhs->var; size_t lindex = symbolIndex.at(lsym); - SymbolIndexMap::const_iterator rsymPair = symbolIndex.find(rsym); - if (rsymPair == symbolIndex.end()) - { - // TODO: A move from a global or extern symbol without a copy-constructor - // call is probably an AST consistency or codegen error, given that - // we have already weeded out fundamental, extern and class types. - return; - } - size_t rindex = rsymPair->second; + size_t rindex = symbolIndex.at(rsym); // Copy ownership state from RHS. INT_ASSERT(gen->get(lindex) == false); @@ -625,7 +627,7 @@ static void computeTransitions(SymExprVector& symExprs, if (isCreated(se)) processCreator(se, gen, symbolIndex); - if (isBitwiseCopy(se)) + if (isBitwiseCopy(se, symbolIndex)) { processBitwiseCopy(se, gen, symbolIndex); createAlias(se, aliases); @@ -696,16 +698,6 @@ static void insertAutoCopy(SymExpr* se) if (autoCopyFn == NULL) return; -// This clause is commented out, because when arrays were inserted into tuples, -// their refcounts were not getting bumped, but when the tuple is deleted, the -// array is deleted and that decrements the reference count. -#if 0 - // For now, we ignore internally reference-counted types. We'll add the - // code to manage those later. - if (isRefCountedType(sym->type)) - return; -#endif - // Prevent autoCopy functions from calling themselves recursively. // TODO: Remove this clause after the autoCopy function becomes a copy constructor // method. @@ -739,7 +731,7 @@ static void insertAutoCopy(SymExprVector& symExprs, BitVec* gen, BitVec* kill, processCreator(se, gen, symbolIndex); // Pass ownership to this symbol if it is the result of a bitwise copy. - if (isBitwiseCopy(se)) + if (isBitwiseCopy(se, symbolIndex)) processBitwiseCopy(se, gen, symbolIndex); if (isConsumed(se)) diff --git a/test/classes/diten/record_initialize.good b/test/classes/diten/record_initialize.good index e2ce865036f1..77897d4ced0a 100644 --- a/test/classes/diten/record_initialize.good +++ b/test/classes/diten/record_initialize.good @@ -1,3 +1,26 @@ BBB.initialize() +BBB.initialize() +BBB.initialize() +AAA.initialize() +BBB.initialize() +BBB.initialize() +BBB.initialize() +AAA.initialize() +BBB.initialize() +BBB.initialize() +BBB.initialize() +AAA.initialize() +BBB.initialize() +BBB.initialize() +BBB.initialize() +AAA.initialize() +BBB.initialize() +BBB.initialize() +BBB.initialize() +AAA.initialize() +BBB.initialize() +BBB.initialize() +BBB.initialize() AAA.initialize() -(b = (a = 0, b = 0, c = 0)) +(b = BBB.initialize() +(a = 0, b = 0, c = 0)) diff --git a/test/types/records/hilde/newUserDefaultCtor.good b/test/types/records/hilde/newUserDefaultCtor.good index ab72fd6b1ea8..f05b28394051 100644 --- a/test/types/records/hilde/newUserDefaultCtor.good +++ b/test/types/records/hilde/newUserDefaultCtor.good @@ -1,5 +1,5 @@ Called R(). Called R(). -Called ~R(). Done. Called ~R(). +Called ~R(). diff --git a/test/users/ferguson/refcnt.good b/test/users/ferguson/refcnt.good index 2ddd6727f38d..e0a04a721dfe 100644 --- a/test/users/ferguson/refcnt.good +++ b/test/users/ferguson/refcnt.good @@ -7,8 +7,6 @@ Starting test_one {count = 1} In R.routine {count = 1} In test_two {count = 1} Done test_one {count = 1} -In ~R() 1 -Destroying a RefCount After scope Before scope (copy initialization) Initializing a RefCount @@ -20,9 +18,6 @@ Starting test_one {count = 2} In R.routine {count = 2} In test_two {count = 2} Done test_one {count = 2} -In ~R() 2 -In ~R() 1 -Destroying a RefCount After scope (copy initialization) Before scope (copy initialization 2) Initializing a RefCount @@ -31,9 +26,6 @@ Starting test_one {count = 2} In R.routine {count = 2} In test_two {count = 2} Done test_one {count = 2} -In ~R() 2 -In ~R() 1 -Destroying a RefCount After scope (copy initialization 2) Before scope (setting global) Initializing a RefCount @@ -44,7 +36,6 @@ Starting R assign {count = 1}{count = 1} Destroying a RefCount Done R assign {count = 2} Done test_one {count = 2} -In ~R() 2 After scope (setting global) Before scope (begin) Initializing a RefCount @@ -54,9 +45,18 @@ In R.routine {count = 2} In test_two {count = 2} Done test_one {count = 2} In ~R() 2 -In ~R() 1 -Destroying a RefCount After scope (begin) Finishing In ~R() 1 Destroying a RefCount +In ~R() 2 +In ~R() 1 +Destroying a RefCount +In ~R() 2 +In ~R() 1 +Destroying a RefCount +In ~R() 2 +In ~R() 1 +Destroying a RefCount +In ~R() 1 +Destroying a RefCount From 491c4a993262ad6abdf4c66ee2272a8957be99f5 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Thu, 8 Jan 2015 08:32:28 -0600 Subject: [PATCH 42/53] Set initial OUT values to all ones for flow analysis. Add some debug prinout clauses. Down to 62 regressions. --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index 7b568857e52c..8ff59cc3af3e 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -929,10 +929,28 @@ static void insertAutoCopyAutoDestroy(FnSymbol* fn) computeTransitions(fn, GEN, KILL, aliases, symbolIndex); +#if DEBUG_AMM + if (debug > 0) + { + printf("GEN:\n"); BasicBlock::printBitVectorSets(GEN); + printf("KILL:\n"); BasicBlock::printBitVectorSets(KILL); + } +#endif + // We can reuse the existing forward flow analysis to compute how ownership // flows through the basic blocks. + // The OUT sets are initially all ones. + for(size_t i = 0; i < nbbs; ++i) OUT[i]->set(); BasicBlock::forwardFlowAnalysis(fn, GEN, KILL, IN, OUT, true); +#if DEBUG_AMM + if (debug > 0) + { + printf("IN:\n"); BasicBlock::printBitVectorSets(IN); + printf("OUT:\n"); BasicBlock::printBitVectorSets(OUT); + } +#endif + insertAutoCopy(fn, GEN, KILL, IN, OUT, symbols, symbolIndex, aliases); // We need our own equation for backward flow. @@ -943,6 +961,14 @@ static void insertAutoCopyAutoDestroy(FnSymbol* fn) // block. backwardFlowOwnership(*fn->basicBlocks, IN, OUT); +#if DEBUG_AMM + if (debug > 0) + { + printf("IN:\n"); BasicBlock::printBitVectorSets(IN); + printf("OUT:\n"); BasicBlock::printBitVectorSets(OUT); + } +#endif + insertAutoDestroy(fn, GEN, KILL, IN, OUT, symbols, symbolIndex, aliases); destroyFlowSet(GEN); From ac230f7efd3b11b29fdd2cca81a26d4f2a93e722 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Thu, 8 Jan 2015 13:03:07 -0600 Subject: [PATCH 43/53] Changes to support PGI compiler and new bitVec interface. --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 72 ++++++++++++------- 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index 8ff59cc3af3e..0ca9b95c1e9f 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -218,6 +218,7 @@ class AliasVectorMap : public std::map AliasVectorMap() : std::map() {} void insert(Symbol* sym); + SymbolVector* at(Symbol* sym) const; void merge(Symbol* orig, Symbol* alias); }; @@ -235,7 +236,7 @@ AliasVectorMap::~AliasVectorMap() SymbolVector* aliasList = it->second; for_vector(Symbol, sym, *aliasList) - at(sym) = NULL; + operator[](sym) = NULL; delete aliasList; } @@ -252,12 +253,22 @@ AliasVectorMap::insert(Symbol* sym) operator[](sym) = aliasList; } +// Retrieves the SymbolVector* store in the selected element if it exists; +// returns null otherwise. +SymbolVector* AliasVectorMap::at(Symbol* sym) const +{ + const_iterator e = find(sym); + if (e == end()) + return NULL; + return e->second; +} + // Merge the alias lists of two symbols that have become aliases. void AliasVectorMap::merge(Symbol* orig, Symbol* alias) { - SymbolVector* origList = at(orig); - SymbolVector* aliasList = at(alias); + SymbolVector* origList = operator[](orig); + SymbolVector* aliasList = operator[](alias); if (aliasList == origList) { @@ -409,7 +420,7 @@ static bool isCreated(SymExpr* se) // Returns true if the expression in which the given SymExpr appears makes it a // bitwise copy of some other symbol; false otherwise. static bool isBitwiseCopy(SymExpr* se, - const SymbolIndexMap& symbolIndex) + SymbolIndexMap& symbolIndex) { // Must be a call (as opposed to a DefExpr or LabelExpr, etc. if (CallExpr* call = toCallExpr(se->parentExpr)) @@ -515,11 +526,11 @@ static bool isJump(Expr* stmt) static void processCreator(SymExpr* se, BitVec* gen, - const SymbolIndexMap& symbolIndex) + SymbolIndexMap& symbolIndex) { // Any function returning a value is considered to be a constructor. Symbol* sym = se->var; - size_t index = symbolIndex.at(sym); + size_t index = symbolIndex[sym]; // We expect that each symbol gets constructed only once, so if we are // about to set a bit in the gen set, it cannot already be true. @@ -535,7 +546,7 @@ static void processCreator(SymExpr* se, static void processBitwiseCopy(SymExpr* se, BitVec* gen, - const SymbolIndexMap& symbolIndex) + SymbolIndexMap& symbolIndex) { CallExpr* call = toCallExpr(se->parentExpr); @@ -545,8 +556,8 @@ static void processBitwiseCopy(SymExpr* se, BitVec* gen, Symbol* lsym = lhs->var; Symbol* rsym = rhs->var; - size_t lindex = symbolIndex.at(lsym); - size_t rindex = symbolIndex.at(rsym); + size_t lindex = symbolIndex[lsym]; + size_t rindex = symbolIndex[rsym]; // Copy ownership state from RHS. INT_ASSERT(gen->get(lindex) == false); @@ -592,7 +603,7 @@ static void createAlias(SymExpr* se, AliasVectorMap& aliases) // kill set and remove them from the gen set. static void processConsumer(SymExpr* se, BitVec* gen, BitVec* kill, const AliasVectorMap& aliases, - const SymbolIndexMap& symbolIndex) + SymbolIndexMap& symbolIndex) { // Add all members of an alias clique to the kill set and remove them from // the gen set. @@ -604,7 +615,7 @@ static void processConsumer(SymExpr* se, BitVec* gen, BitVec* kill, for_vector(Symbol, alias, *aliasList) { - size_t index = symbolIndex.at(alias); + size_t index = symbolIndex[alias]; gen->reset(index); kill->set(index); } @@ -614,7 +625,7 @@ static void processConsumer(SymExpr* se, BitVec* gen, BitVec* kill, static void computeTransitions(SymExprVector& symExprs, BitVec* gen, BitVec* kill, AliasVectorMap& aliases, - const SymbolIndexMap& symbolIndex) + SymbolIndexMap& symbolIndex) { for_vector(SymExpr, se, symExprs) { @@ -647,7 +658,7 @@ static void computeTransitions(SymExprVector& symExprs, // the first (receiver) argument. static void computeTransitions(BasicBlock& bb, BitVec* gen, BitVec* kill, AliasVectorMap& aliases, - const SymbolIndexMap& symbolIndex) + SymbolIndexMap& symbolIndex) { for_vector(Expr, expr, bb.exprs) { @@ -665,7 +676,7 @@ static void computeTransitions(BasicBlock& bb, BitVec* gen, BitVec* kill, static void computeTransitions(FnSymbol* fn, FlowSet& GEN, FlowSet& KILL, AliasVectorMap& aliases, - const SymbolIndexMap& symbolIndex) + SymbolIndexMap& symbolIndex) { size_t nbbs = fn->basicBlocks->size(); for (size_t i = 0; i < nbbs; ++i) @@ -716,7 +727,7 @@ static void insertAutoCopy(SymExpr* se) static void insertAutoCopy(SymExprVector& symExprs, BitVec* gen, BitVec* kill, const AliasVectorMap& aliases, - const SymbolIndexMap& symbolIndex) + SymbolIndexMap& symbolIndex) { for_vector(SymExpr, se, symExprs) { @@ -738,7 +749,7 @@ static void insertAutoCopy(SymExprVector& symExprs, BitVec* gen, BitVec* kill, { // If the gen bit is set for this symbol, we can leave it as a move and // transfer ownership. Otherwise, we need to insert an autoCopy. - size_t index = symbolIndex.at(sym); + size_t index = symbolIndex[sym]; if (!gen->get(index)) insertAutoCopy(se); @@ -751,7 +762,7 @@ static void insertAutoCopy(SymExprVector& symExprs, BitVec* gen, BitVec* kill, static void insertAutoCopy(BasicBlock& bb, BitVec* gen, BitVec* kill, const SymbolVector& symbols, - const SymbolIndexMap& symbolIndex, + SymbolIndexMap& symbolIndex, const AliasVectorMap& aliases) { for_vector(Expr, expr, bb.exprs) @@ -768,7 +779,7 @@ static void insertAutoCopy(FnSymbol* fn, FlowSet& GEN, FlowSet& KILL, FlowSet& IN, FlowSet& OUT, const SymbolVector& symbols, - const SymbolIndexMap& symbolIndex, + SymbolIndexMap& symbolIndex, const AliasVectorMap& aliases) { size_t nbbs = fn->basicBlocks->size(); @@ -793,7 +804,7 @@ static void insertAutoCopy(FnSymbol* fn, // specified by the given bit-vector. static void insertAutoDestroy(BasicBlock& bb, BitVec* to_kill, const SymbolVector& symbols, - const SymbolIndexMap& symbolIndex, + SymbolIndexMap& symbolIndex, const AliasVectorMap& aliases) { // Skip degenerate basic blocks. @@ -818,7 +829,7 @@ static void insertAutoDestroy(BasicBlock& bb, BitVec* to_kill, SymbolVector* aliasList = aliases.at(sym); for_vector(Symbol, alias, *aliasList) { - size_t index = symbolIndex.at(alias); + size_t index = symbolIndex[alias]; to_kill->reset(index); } @@ -850,7 +861,7 @@ static void insertAutoDestroy(FnSymbol* fn, FlowSet& GEN, FlowSet& KILL, FlowSet& IN, FlowSet& OUT, const SymbolVector& symbols, - const SymbolIndexMap& symbolIndex, + SymbolIndexMap& symbolIndex, const AliasVectorMap& aliases) { size_t nbbs = fn->basicBlocks->size(); @@ -859,9 +870,8 @@ static void insertAutoDestroy(FnSymbol* fn, // We need to insert an autodestroy call for each symbol that is owned // (live) at the end of the block but is unowned (dead) in the OUT set. BasicBlock& bb = *(*fn->basicBlocks)[i]; - BitVec* to_kill = *IN[i] + *GEN[i] - *KILL[i] - *OUT[i]; - insertAutoDestroy(bb, to_kill, symbols, symbolIndex, aliases); - delete to_kill; to_kill = 0; + BitVec to_kill = *IN[i] + *GEN[i] - *KILL[i] - *OUT[i]; + insertAutoDestroy(bb, &to_kill, symbols, symbolIndex, aliases); } } @@ -989,3 +999,17 @@ void insertAutoCopyAutoDestroy() insertAutoCopyAutoDestroy(fn); } } + + +// TODO: In several places, we use std::map<,>::operator[]() where we would +// really like to use std::map<>::at(). The latter contains an assertion that +// the element being accessed is already a member of the map while the former +// just inserts a new element if none is present. However, at() is only +// available in the C++11 version of the STL and PGI does not support that +// version (as of this writing). Using find() and then [] is a bit wordy, so +// we just use [] with fingers crossed for now. + +// TODO: In several places, we would like to pass SymbolIndex& with a const +// qualifier, but since operator[] in the C++98 STL is not a const member +// function, we are prevented from doing so until we adopt the C++11 STL +// uniformly. From 53eeb26f5d00bb0e07ebea7be4f10ef422573836 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Thu, 8 Jan 2015 13:05:18 -0600 Subject: [PATCH 44/53] Update type/records/const-checking/constructors.good for increase autoCopy calls. --- .../records/const-checking/constructors.good | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/test/types/records/const-checking/constructors.good b/test/types/records/const-checking/constructors.good index b26bcb8c70c5..c118ad1800ec 100644 --- a/test/types/records/const-checking/constructors.good +++ b/test/types/records/const-checking/constructors.good @@ -6,11 +6,31 @@ RECTYPE.initialize() RECTYPE.initRT() RECTYPE constructor RECTYPE.initRT() -(VARFIELD = 401, CONSTFIELD = 901) +RECTYPE.initialize() +RECTYPE.initRT() +RECTYPE.initialize() +RECTYPE.initRT() +RECTYPE.initialize() +RECTYPE.initRT() +RECTYPE.initialize() +RECTYPE.initRT() +RECTYPE.initialize() +RECTYPE.initRT() +(VARFIELD = 12863, CONSTFIELD = 219064) RECTYPE.initialize() RECTYPE.initRT() RECTYPE constructor RECTYPE.initRT() -(VARFIELD = 401, CONSTFIELD = 901) +RECTYPE.initialize() +RECTYPE.initRT() +RECTYPE.initialize() +RECTYPE.initRT() +RECTYPE.initialize() +RECTYPE.initRT() +RECTYPE.initialize() +RECTYPE.initRT() +RECTYPE.initialize() +RECTYPE.initRT() +(VARFIELD = 12863, CONSTFIELD = 219064) From 68784a793c45365ed05150c6d547814ff4738128 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Thu, 8 Jan 2015 18:08:03 -0600 Subject: [PATCH 45/53] Insert autoDestroy calls ahead of _downEndCount calls. 52 regressions remaining (all execution-time) --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 96 +++++++++++++++++-- 1 file changed, 86 insertions(+), 10 deletions(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index 0ca9b95c1e9f..bedeeffbb22a 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -506,10 +506,25 @@ static bool isConsumed(SymExpr* se) return false; } - -// This predicate determines if the given statement is a jump. -static bool isJump(Expr* stmt) +#if 0 +static bool isCStyleForLoopUpdateBlock(BasicBlock* bb) +{ + Expr*& first = bb->exprs.front(); + Expr*& last = bb->exprs.back(); + if (Expr* parent = getCommonParentExpr(first, last)) + { + if (CForLoop* cfl = dynamic_cast(parent->parentExpr)) + { + if (cfl->incrBlockGet() == parent) + return true; + } + } + return false; +} +#endif + +static bool isFlowStmt(Expr* stmt) { // A goto is definitely a jump. if (isGotoStmt(stmt)) @@ -517,13 +532,67 @@ static bool isJump(Expr* stmt) // A return primitive works like a jump. (Nothing should appear after it.) if (CallExpr* call = toCallExpr(stmt)) + { if (call->isPrimitive(PRIM_RETURN)) return true; + // _downEndCount is treated like a flow statement because we do not want to + // insert autoDestroys after the task says "I'm done." This can result in + // false-positive memory allocation errors because the waiting (parent + // task) can then proceed to test that the subtask has not leaked before + // the subtask release locally-(dynamically-)allocated memory. + if (FnSymbol* fn = call->isResolved()) + if (!strcmp(fn->name, "_downEndCount")) + return true; + } return false; } +// Finds the last "normal" statement in a basic block and returns it. +// A non-normal statement is a flow-altering statement, like a jump or return. +// +// If the block contains no "normal" statements, then NULL is returned. +// Then, new code can be inserted ahead of the first statement in the block. +static Expr* getLastNonflowStatement(BasicBlock* bb) +{ +#if 0 + // As a slight code cleanup, autoDestroys that appear at the end of a loop + // body (after the increment expression) in a C-style for loop are pushed + // back into the preceding block. + // Without this clause, we get C-sytle for loops that look like: + // /* 980641 */ for (i3 = start2; ((i3 <= end2)); i3 += INT64(1),chpl__autoDestroy23(&_yieldedIndex2),chpl__autoDestroy23(&_yieldedIndex),chpl__autoDestroy23(&call_tmp13)) { + // which is arguably correct, but not very pretty. + // This code depends on the increment expression in a C-style for loop being + // set off in a block by itself in basic block analysis. (Look for clauses + // related to CForLoop blocks in BasicBlock::buildLoopStmt().) + if (isCStyleForLoopUpdateBlock(bb)) + { + // Back up to the predecessor of the C-style for loop update block and use + // its last statement as the insertion point instead. + + // We assume that the update expression of a C-style for loop has only one + // predecessor. (This would not be true if we supported continue + // statements in C-sytle for loops.) + INT_ASSERT(bb->ins.size() == 1); + bb = bb->ins.at(0); + } +#endif + + size_t i = bb->exprs.size(); + while (i-- > 0) + { + Expr*& expr = bb->exprs[i]; + // TODO: It might be good to insert only statements into the bb expr list. + Expr* stmt = expr->getStmtExpr(); // Is this necessary? + if (! isFlowStmt(stmt)) + return stmt; + } + + return NULL; +} + + static void processCreator(SymExpr* se, BitVec* gen, SymbolIndexMap& symbolIndex) @@ -802,19 +871,26 @@ static void insertAutoCopy(FnSymbol* fn, // At the end of this basic block, insert an autodestroy for each symbol // specified by the given bit-vector. -static void insertAutoDestroy(BasicBlock& bb, BitVec* to_kill, +static void insertAutoDestroy(BasicBlock* bb, BitVec* to_kill, const SymbolVector& symbols, SymbolIndexMap& symbolIndex, const AliasVectorMap& aliases) { // Skip degenerate basic blocks. - if (bb.exprs.size() == 0) + if (bb->exprs.size() == 0) return; // Find the last statement in the block. - Expr*& last = bb.exprs.back(); - Expr* stmt = last->getStmtExpr(); - bool isjump = isJump(stmt); + Expr* stmt = getLastNonflowStatement(bb); + + // If we didn't find one, use the first statement in the block, and insert + // autoDestroy calls in front of it. + bool isJump = false; + if (stmt == NULL) + { + stmt = bb->exprs[0]; + isJump = true; + } // For each true bit in the bit vector, add an autodestroy call. // But destroying one member of an alias list destroys them all. @@ -846,7 +922,7 @@ static void insertAutoDestroy(BasicBlock& bb, BitVec* to_kill, CallExpr* autoDestroyCall = new CallExpr(autoDestroy, sym); - if (isjump) + if (isJump) stmt->insertBefore(autoDestroyCall); else stmt->insertAfter(autoDestroyCall); @@ -869,7 +945,7 @@ static void insertAutoDestroy(FnSymbol* fn, { // We need to insert an autodestroy call for each symbol that is owned // (live) at the end of the block but is unowned (dead) in the OUT set. - BasicBlock& bb = *(*fn->basicBlocks)[i]; + BasicBlock* bb = (*fn->basicBlocks)[i]; BitVec to_kill = *IN[i] + *GEN[i] - *KILL[i] - *OUT[i]; insertAutoDestroy(bb, &to_kill, symbols, symbolIndex, aliases); } From 287745a82f24ff8ed0c0597361e2a9e35e81a0a9 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Fri, 9 Jan 2015 02:12:10 -0600 Subject: [PATCH 46/53] Added PRIM_GET_MEMBER_VALUE to list of primitives excluded from those that return owned values. Down to 50 regressions. --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 4 ++++ test/users/ferguson/refcnt.good | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index bedeeffbb22a..fff2b1f3b68d 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -405,6 +405,10 @@ static bool isCreated(SymExpr* se) // Because the operand is a reference, its contents are "unowned" by the // result. An autoCopy needs to be inserted where an owned copy is required. return false; + case PRIM_GET_MEMBER_VALUE: + // Returns a bitwise copy of the referenced field which is, as a + // consequence, unowned. + return false; } } return true; diff --git a/test/users/ferguson/refcnt.good b/test/users/ferguson/refcnt.good index e0a04a721dfe..c1f39b849f0e 100644 --- a/test/users/ferguson/refcnt.good +++ b/test/users/ferguson/refcnt.good @@ -50,12 +50,12 @@ Finishing In ~R() 1 Destroying a RefCount In ~R() 2 +In ~R() 2 In ~R() 1 Destroying a RefCount In ~R() 2 In ~R() 1 Destroying a RefCount -In ~R() 2 In ~R() 1 Destroying a RefCount In ~R() 1 From 2beaa5f4d202dd484e11d3190adcab22b1740bf3 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Fri, 9 Jan 2015 15:04:05 -0600 Subject: [PATCH 47/53] Output of types/records/sungeun/destructor3 changed due to tuple fields being autoCopied now. --- test/types/records/sungeun/destructor3.good | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/types/records/sungeun/destructor3.good b/test/types/records/sungeun/destructor3.good index 359604d1ae7b..29a407cfedef 100644 --- a/test/types/records/sungeun/destructor3.good +++ b/test/types/records/sungeun/destructor3.good @@ -1,2 +1,6 @@ -(x = 0) +(x = 0)bye + +bye +bye +bye bye From 3cbf489fd0fadd7c64818ed69ffcd770eb7f156c Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Sun, 11 Jan 2015 00:53:05 -0600 Subject: [PATCH 48/53] Fix BB analysis bug: Empty blocks with no predecessors Added cleanup traversal to remove empty blocks. (Also control verification with the fVerify flag now.) Revised the verify function to lock this behavior in. The removal of empty blocks cannot be done until BB analysis is complete, because some of the threading is delayed. We have to wait for threading to finish before we can identify blocks that have no predecessor. Updated the signature of initInSets in copyPropagation.cpp. --- compiler/AST/bb.cpp | 62 +++++++++++++++++++++- compiler/include/bb.h | 1 + compiler/optimizations/copyPropagation.cpp | 4 +- 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/compiler/AST/bb.cpp b/compiler/AST/bb.cpp index 908a009c83ff..e52435ab2725 100644 --- a/compiler/AST/bb.cpp +++ b/compiler/AST/bb.cpp @@ -71,7 +71,10 @@ void BasicBlock::buildBasicBlocks(FnSymbol* fn) { fn->basicBlocks->push_back(BasicBlock::steal()); - INT_ASSERT(verifyBasicBlocks(fn)); + removeEmptyBlocks(fn); + + if (fVerify) + INT_ASSERT(verifyBasicBlocks(fn)); } BasicBlock* BasicBlock::steal() { @@ -202,9 +205,11 @@ void BasicBlock::buildBasicBlocks(FnSymbol* fn, Expr* stmt, bool mark) { LabelSymbol* label = toLabelSymbol(toSymExpr(s->label)->var); if (BasicBlock* bb = labelMaps.get(label)) { + // Thread this block to its destination label. thread(basicBlock, bb); } else { + // Set up goto map, so this block's successor can be back-patched later. std::vector* vbb = gotoMaps.get(label); if (!vbb) @@ -216,6 +221,10 @@ void BasicBlock::buildBasicBlocks(FnSymbol* fn, Expr* stmt, bool mark) { } append(s, mark); // Put the goto at the end of its block. + + // We need a new block, so we can not thread the one containing the goto to + // this new one. There is a break in the flow. If the new block does not + // begin with a label, it is unreachable and can be removed. restart(fn); } else { @@ -291,9 +300,49 @@ void BasicBlock::thread(BasicBlock* src, BasicBlock* dst) { src->outs.push_back(dst); } +// Look for and remove empty blocks with no predecessor and whose successor is +// the next block in sequence. These blocks get created when a block ends in a +// goto statement and the enclosing construct calls restart immediately. +// We have to wait until basic block analysis is done, because we don't know if +// a block has predecessors until after threading is performed, and this is +// sometimes delayed. +void BasicBlock::removeEmptyBlocks(FnSymbol* fn) +{ + // Create a new vector that contains just the items we want to preserve. + int new_id = 0; + BasicBlockVector* new_blocks = new BasicBlockVector(); + for_vector(BasicBlock, bb, *fn->basicBlocks) + { + // Skip empty blocks with no predecessors. + if (bb->ins.size() == 0 && + bb->exprs.size() == 0) + { + // This block will be removed. It is no longer a predecessor of anyone, + // so we must update the back links. + for_vector(BasicBlock, succ, bb->outs) + { + BasicBlockVector::iterator i; + for (i = succ->ins.begin(); i != succ->ins.end(); ++i) + if (*i == bb) + break; + + INT_ASSERT(i != succ->ins.end()); + succ->ins.erase(i); + } + } + else + { + bb->id = new_id++; + new_blocks->push_back(bb); + } + } + delete fn->basicBlocks; fn->basicBlocks = new_blocks; +} + // Returns true if the basic block structure is OK, false otherwise. bool BasicBlock::verifyBasicBlocks(FnSymbol* fn) { - for_vector(BasicBlock, bb, *fn->basicBlocks) { + for_vector(BasicBlock, bb, *fn->basicBlocks) + { if (bb->isOK() == false) return false; } @@ -307,6 +356,15 @@ bool BasicBlock::isOK() { if (exprs.size() != marks.size()) return false; + // Every empty interior block must have a predecessor. + // Non-empty blocks with no predecessors are dead code, which may be + // removed by a client of BB analysis. These dead blocks cannot be + // identified without BB analysis, so non-empty blocks with no + // predecessors are valid. + if (ins.size() == 0 && + exprs.size() == 0) + return false; + // Expressions must be live (non-NULL); for_vector(Expr, expr, exprs) if (expr == 0) diff --git a/compiler/include/bb.h b/compiler/include/bb.h index d3e49c0a5ec6..676265104280 100644 --- a/compiler/include/bb.h +++ b/compiler/include/bb.h @@ -101,6 +101,7 @@ class BasicBlock static BasicBlock* steal(); + static void removeEmptyBlocks(FnSymbol* fn); static bool verifyBasicBlocks(FnSymbol* fn); static int nextID; diff --git a/compiler/optimizations/copyPropagation.cpp b/compiler/optimizations/copyPropagation.cpp index f62ea57b7acc..cc8a416f457a 100644 --- a/compiler/optimizations/copyPropagation.cpp +++ b/compiler/optimizations/copyPropagation.cpp @@ -992,8 +992,10 @@ static void initCopySets(std::vector& COPY, std::vector& ends, // When these are corrected and the test becomes true, then we can drop back // to the simpler form given here: #ifdef INLINING_DOES_NOT_LEAVE_INTERNAL_BASIC_BLOCKS_WITHOUT_PREDECESSORS -static void initInSets(std::vector& IN, size_t nbbs) +static void initInSets(std::vector& IN, FnSymbol* fn) { + size_t nbbs = fn->basicBlocks->size(); + // Note that we start with i = 1, so that IN[0] is left as all zeroes. for (size_t i = 1; i < nbbs; i++) IN[i]->set(); From d7a2f1888818d0060a319e33c9f5448b4b103bf6 Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Mon, 12 Jan 2015 15:14:51 -0600 Subject: [PATCH 49/53] Add code to avoid copying a domain record if the domain class it contains is nil. Also, update test/types/records/sungeun/classInRecord.chpl and test/types/records/sungeun/destructor3.good because there are different numbers of calls to chpl__autoCopy and chpl__autoDestroy. --- modules/internal/ChapelArray.chpl | 9 ++++++++- test/types/records/sungeun/classInRecord.chpl | 8 ++++++++ test/types/records/sungeun/destructor3.good | 1 + 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/modules/internal/ChapelArray.chpl b/modules/internal/ChapelArray.chpl index f3e513475910..a72faa676afc 100644 --- a/modules/internal/ChapelArray.chpl +++ b/modules/internal/ChapelArray.chpl @@ -2110,7 +2110,14 @@ module ChapelArray { } proc =(ref a: domain, b: domain) { - if !isIrregularDom(a) && !isIrregularDom(b) { + if (b._value == nil) { + // In some places, we use assignment where we should be using initialization. + // When the RHS is nil (uninitialized), it is permissible to do nothing + // if the left side is also nil. + if (a._value != nil) then + halt("Cannot overwrite a valid domain with 'nil'."); + } + else if !isIrregularDom(a) && !isIrregularDom(b) { for e in a._value._arrs do { on e do e.dsiReallocate(b); } diff --git a/test/types/records/sungeun/classInRecord.chpl b/test/types/records/sungeun/classInRecord.chpl index 9f495777e46e..cdd82b7c6360 100644 --- a/test/types/records/sungeun/classInRecord.chpl +++ b/test/types/records/sungeun/classInRecord.chpl @@ -9,6 +9,14 @@ record myR { proc ~myR() { delete c; } } +pragma "auto copy fn" +inline proc chpl__autoCopy(x: myR) +{ + var r:myR; + r = x; + return r; +} + pragma "auto destroy fn" inline proc chpl__autoDestroy(x: myR) { } diff --git a/test/types/records/sungeun/destructor3.good b/test/types/records/sungeun/destructor3.good index 29a407cfedef..e0cf5cce4cb9 100644 --- a/test/types/records/sungeun/destructor3.good +++ b/test/types/records/sungeun/destructor3.good @@ -4,3 +4,4 @@ bye bye bye bye +bye From 14de65c54e07040c2aef26b4c435faa406d239ff Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Mon, 12 Jan 2015 15:26:57 -0600 Subject: [PATCH 50/53] Update test/classes/figueroa/RecordConstructor2.good due to increased autoCopy callse. This test basically devolves to a test on whether the members of a tuple are passed by reference or value. In the current version of the grand autoCopy/autoDestroy rework, a tuple owns and destroys record members that are passed by value. The output of this test is consistent with that definition. In the previous implementation, MM in tuples was something of a hybrid. (Record) values copied when the fields in a tuple were created and destroyed when done, but (inconsistently) the members of a tuple are not copied when a tuple is assigned or when one of its members is read out. This is the root cause of the return-record bug this work is intended to fix. However, this solution is not necessarily permanent. It may be possible to avoid these copies by handling tuples specially. In that special implementation, tuple members would always be treated as references. But if a member of a tuple is used to determine the type of a result, members that were originally values (i.e. not references) would have value and not reference type. There are some semantical issues related to this choice that may need to be worked through (or experimented with). Down to 43 regressions. --- test/classes/figueroa/RecordConstructor2.good | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/classes/figueroa/RecordConstructor2.good b/test/classes/figueroa/RecordConstructor2.good index a2cfb0cdf281..f8c361347f3c 100644 --- a/test/classes/figueroa/RecordConstructor2.good +++ b/test/classes/figueroa/RecordConstructor2.good @@ -1,3 +1,3 @@ (c = 1) (c = 2) -(c = 1) +(c = 2) 1 From e9c9445f0473dc5d6c6c16aab1f367197216abce Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Mon, 12 Jan 2015 19:54:11 -0600 Subject: [PATCH 51/53] Add an explicit default constructor for record channel in IO.chpl. Down to 45 regressions. --- compiler/resolution/wrappers.cpp | 1 + modules/standard/IO.chpl | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/compiler/resolution/wrappers.cpp b/compiler/resolution/wrappers.cpp index 5662d8f3984e..278d84e42c2a 100644 --- a/compiler/resolution/wrappers.cpp +++ b/compiler/resolution/wrappers.cpp @@ -187,6 +187,7 @@ buildDefaultWrapper(FnSymbol* fn, wrapper->insertAtTail(new CallExpr(PRIM_SETCID, wrapper->_this)); } } + // This call is required to establish the type of _this. wrapper->insertAtTail(new CallExpr(PRIM_INIT_FIELDS, wrapper->_this)); } CallExpr* call = new CallExpr(fn); diff --git a/modules/standard/IO.chpl b/modules/standard/IO.chpl index daeac00da527..05d6df47e246 100644 --- a/modules/standard/IO.chpl +++ b/modules/standard/IO.chpl @@ -841,6 +841,17 @@ record channel { var _channel_internal:qio_channel_ptr_t = QIO_CHANNEL_PTR_NULL; } +// This is a workaround for initializeClass not default-initializing the +// _channel_internal field in a channel according to the above field-default +// (QIO_CHANNEL_PTR_NULL) because the type qio_channel_ptr_t is external. +// But also, that code should probably use the given initializer value rather +// than the type default. +proc channel.channel(param writing: bool, param kind: iokind, param locking: bool) +{ + home = nil; + _channel_internal = QIO_CHANNEL_PTR_NULL; +} + // TODO -- shouldn't have to write this this way! pragma "init copy fn" proc chpl__initCopy(x: channel) { From 005933cb6314899646e4428036d5b60cd00eed8b Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Tue, 13 Jan 2015 11:43:33 -0600 Subject: [PATCH 52/53] Disabled initializeClass. Nested calls to initializeClass() result in the creation of temporaries which are then piped through chpl__autoCopy to initialize fields in the containing record or class. When the field type is an extern type, the initializeClass() does nothing. This can be bad in cases where an initial value (such as nil) is expected due to the class (or record) invariants. It turns out that we don't need initializeClass() anyway, since it is used only in specializations of the compiler-supplied all-fields constructor. These specializations should ensure that all fields are initialized, so code generated by initializeClass() should be moot. (Testing indicates that this is the case, but I have not yet extensively studied the generated code.) I tried earlier to remove the PRIM_INIT_FIELDS primitive entirely, but apparently it is still needed to establish the type of _this within the specialized all-fields constructor. I reverted the change to IO.chpl, since it did not actually resolve the problem. Down to 30 regressions. --- compiler/resolution/functionResolution.cpp | 6 ++++-- modules/standard/IO.chpl | 11 ----------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/compiler/resolution/functionResolution.cpp b/compiler/resolution/functionResolution.cpp index 2ab68d6d2640..99dbbfb41231 100644 --- a/compiler/resolution/functionResolution.cpp +++ b/compiler/resolution/functionResolution.cpp @@ -374,7 +374,7 @@ static void resolveRecordInitializers(); static void insertDynamicDispatchCalls(); static Type* buildRuntimeTypeInfo(FnSymbol* fn); static void insertReturnTemps(); -static void initializeClass(Expr* stmt, Symbol* sym); +// static void initializeClass(Expr* stmt, Symbol* sym); static void handleRuntimeTypes(); static void pruneResolvedTree(); static void removeCompilerWarnings(); @@ -7266,6 +7266,7 @@ static void insertReturnTemps() { } +#if 0 // // insert code to initialize a class or record // @@ -7287,6 +7288,7 @@ initializeClass(Expr* stmt, Symbol* sym) { } } } +#endif static void handleRuntimeTypes() @@ -8041,7 +8043,7 @@ static void expandInitFieldPrims() forv_Vec(CallExpr, call, gCallExprs) { if (call->isPrimitive(PRIM_INIT_FIELDS)) { - initializeClass(call, toSymExpr(call->get(1))->var); +// initializeClass(call, toSymExpr(call->get(1))->var); call->remove(); } } diff --git a/modules/standard/IO.chpl b/modules/standard/IO.chpl index 05d6df47e246..daeac00da527 100644 --- a/modules/standard/IO.chpl +++ b/modules/standard/IO.chpl @@ -841,17 +841,6 @@ record channel { var _channel_internal:qio_channel_ptr_t = QIO_CHANNEL_PTR_NULL; } -// This is a workaround for initializeClass not default-initializing the -// _channel_internal field in a channel according to the above field-default -// (QIO_CHANNEL_PTR_NULL) because the type qio_channel_ptr_t is external. -// But also, that code should probably use the given initializer value rather -// than the type default. -proc channel.channel(param writing: bool, param kind: iokind, param locking: bool) -{ - home = nil; - _channel_internal = QIO_CHANNEL_PTR_NULL; -} - // TODO -- shouldn't have to write this this way! pragma "init copy fn" proc chpl__initCopy(x: channel) { From 72bfb0efebbcfe4e866169885c47dc58e66f8a5c Mon Sep 17 00:00:00 2001 From: Thomas H Hildebrandt Date: Wed, 14 Jan 2015 14:14:51 -0600 Subject: [PATCH 53/53] Fix a bug in the insertion of autoDestroy calls Too many autodestroy calls were being inserted because one temp contributing to an alias clique might lie along one path while another lies along a distinct path. Backward flow analysis then considers them both to be dead upon entry to the join node. The constraint that each of these temps be dead on exit from their respective blocks causes autodestroy calls to be inserted. The temps themselves are dead as required, but the other members of the clique get deleted (because they are all aliases) even though their respective bits in the OUT set may be true. This mismatch causes us to get the wrong answer, assuming that one of those other clique members is alive when it is actually dead. To address this, I moved the population of the alias map out to a separate traversal. Now, any manipulation of the flow sets affects all members of an alias clique in concert, whether or not their equivalence has yet been established (through a bitwise copy). The result is that this mode of premature deletion has been eliminated. This change allows the insertion of autoDestroys for internally reference-counted types to be enabled. Doing so, only cause one additional regression to appear. Now at 31 regressions. --- compiler/passes/insertAutoCopyAutoDestroy.cpp | 300 ++++++++++++------ test/classes/diten/record_initialize.good | 1 - 2 files changed, 196 insertions(+), 105 deletions(-) diff --git a/compiler/passes/insertAutoCopyAutoDestroy.cpp b/compiler/passes/insertAutoCopyAutoDestroy.cpp index fff2b1f3b68d..5117755cb5f4 100644 --- a/compiler/passes/insertAutoCopyAutoDestroy.cpp +++ b/compiler/passes/insertAutoCopyAutoDestroy.cpp @@ -293,69 +293,6 @@ AliasVectorMap::merge(Symbol* orig, Symbol* alias) //######################### End of AliasVectorMap ######################### -// Scans the body of the given function and inserts all of the variable and -// argument symbols defined in it into the vector of symbols. Bits in the flow -// analysis bit-vectors correspond to the entries in this vector. -// Also constructs an index map, to make it easier to find the index of a -// symbol in the vector. (Otherwise, a linear search is required.) -// The alias map can also be populated at the same time. -static void -extractSymbols(FnSymbol* fn, - SymbolVector& symbols, - SymbolIndexMap& symbolIndex, - AliasVectorMap& aliases) -{ - DefExprVector defExprs; - collectDefExprsSTL(fn, defExprs); - - for_vector(DefExpr, def, defExprs) - { - Symbol* sym = def->sym; - - // We are interested only in arguments and variables. - if (! (toArgSymbol(sym) || toVarSymbol(sym))) - continue; - - Type* type = sym->type; - - // TODO: Extern record types also do not have constructors and - // destructors. To treat them uniformly, we would have to enforce that - // extern types supply constructors and destructors and/or supply them - // internally. - TypeSymbol* ts = type->symbol; - if (ts->hasFlag(FLAG_EXTERN)) - continue; - - // We are concerned only with record types. - // TODO: This is too bad, because it would be nice to be able to treat all - // value types uniformly. But for that to work, arguments of fundamental - // type must be constructed by having their values piped through a copy - // constructor. Currently, that is not the case. - - // We are interested only in records passed by value. Records passed by - // ref appear be a class in the current AST because _ref(T) is a class type. - AggregateType* at = toAggregateType(type); - if (at == NULL) - // Not an aggregate type, so not a record. - continue; - if (!at->isRecord()) - // Not a record. - continue; - - symbolIndex.insert(SymbolIndexElement(sym, symbols.size())); - symbols.push_back(sym); - - // We expect the symbolIndex to return the index of that symbol in the - // symbols vector. - INT_ASSERT(symbols[symbolIndex[sym]] == sym); - - // Initialize each entry in the alias map with a list of symbols - // containing just the symbol itself. - aliases.insert(sym); - } -} - - //############################## Predicates ############################## //# @@ -487,6 +424,7 @@ static bool isConsumed(SymExpr* se) return true; case PRIM_MOVE: + case PRIM_ASSIGN: { // If the left side of a move is a global, it assumes ownership from the // RHS. We can assume that the RHS is local. @@ -511,6 +449,7 @@ static bool isConsumed(SymExpr* se) return false; } + #if 0 static bool isCStyleForLoopUpdateBlock(BasicBlock* bb) { @@ -528,6 +467,7 @@ static bool isCStyleForLoopUpdateBlock(BasicBlock* bb) } #endif + static bool isFlowStmt(Expr* stmt) { // A goto is definitely a jump. @@ -552,6 +492,138 @@ static bool isFlowStmt(Expr* stmt) return false; } +//# +//# End of predicates +//######################################################################## + + +//# +//# extractSymbols +//# + +// Scans the body of the given function and inserts all of the variable and +// argument symbols defined in it into the vector of symbols. Bits in the flow +// analysis bit-vectors correspond to the entries in this vector. +// Also constructs an index map, to make it easier to find the index of a +// symbol in the vector. (Otherwise, a linear search is required.) +// The alias map can also be populated at the same time. +static void +extractSymbols(FnSymbol* fn, + SymbolVector& symbols, + AliasVectorMap& aliases, + SymbolIndexMap& symbolIndex) +{ + DefExprVector defExprs; + collectDefExprsSTL(fn, defExprs); + + for_vector(DefExpr, def, defExprs) + { + Symbol* sym = def->sym; + + // We are interested only in arguments and variables. + if (! (toArgSymbol(sym) || toVarSymbol(sym))) + continue; + + Type* type = sym->type; + + // TODO: Extern record types also do not have constructors and + // destructors. To treat them uniformly, we would have to enforce that + // extern types supply constructors and destructors and/or supply them + // internally. + TypeSymbol* ts = type->symbol; + if (ts->hasFlag(FLAG_EXTERN)) + continue; + + // We are concerned only with record types. + // TODO: This is too bad, because it would be nice to be able to treat all + // value types uniformly. But for that to work, arguments of fundamental + // type must be constructed by having their values piped through a copy + // constructor. Currently, that is not the case. + + // We are interested only in records passed by value. Records passed by + // ref appear be a class in the current AST because _ref(T) is a class type. + AggregateType* at = toAggregateType(type); + if (at == NULL) + // Not an aggregate type, so not a record. + continue; + if (!at->isRecord()) + // Not a record. + continue; + + symbolIndex.insert(SymbolIndexElement(sym, symbols.size())); + symbols.push_back(sym); + + // We expect the symbolIndex to return the index of that symbol in the + // symbols vector. + INT_ASSERT(symbols[symbolIndex[sym]] == sym); + + // Initialize each entry in the alias map with a list of symbols + // containing just the symbol itself. + aliases.insert(sym); + } +} + + +// +// populateAliases +// + +static void createAlias(SymExpr* se, AliasVectorMap& aliases) +{ + CallExpr* call = toCallExpr(se->parentExpr); + + SymExpr* lhs = toSymExpr(call->get(1)); + SymExpr* rhs = toSymExpr(call->get(2)); + + Symbol* lsym = lhs->var; + Symbol* rsym = rhs->var; + + // Merge aliases whether or not they are live. + aliases.merge(rsym, lsym); +} + + +static void populateAliases(SymExprVector& symExprs, + const SymbolVector& symbols, + AliasVectorMap& aliases, + SymbolIndexMap& symbolIndex) +{ + for_vector(SymExpr, se, symExprs) + { + // We are only interested in local symbols, so if this one does not appear + // in our map, move on. + Symbol* sym = se->var; + if (symbolIndex.find(sym) == symbolIndex.end()) + continue; + + // Pass ownership to this symbol if it is the result of a bitwise copy. + if (isBitwiseCopy(se, symbolIndex)) + createAlias(se, aliases); + } +} + + +static void populateAliases(FnSymbol* fn, + const SymbolVector& symbols, + AliasVectorMap& aliases, + SymbolIndexMap& symbolIndex) +{ + for_vector(BasicBlock, bb, *fn->basicBlocks) + { + for_vector(Expr, expr, bb->exprs) + { + SymExprVector symExprs; + collectSymExprsSTL(expr, symExprs); + populateAliases(symExprs, symbols, aliases, symbolIndex); + } + } +} + + +// +// computeTransitions +// + // Finds the last "normal" statement in a basic block and returns it. // A non-normal statement is a flow-altering statement, like a jump or return. @@ -598,8 +670,9 @@ static Expr* getLastNonflowStatement(BasicBlock* bb) static void processCreator(SymExpr* se, - BitVec* gen, - SymbolIndexMap& symbolIndex) + BitVec* gen, + const AliasVectorMap& aliases, + SymbolIndexMap& symbolIndex) { // Any function returning a value is considered to be a constructor. Symbol* sym = se->var; @@ -614,11 +687,41 @@ static void processCreator(SymExpr* se, if (fWarnOwnership) USR_WARN(sym, "Reinitialization of sym"); - gen->set(index); + // When one member of an alias set is defined, we treat them all as being + // defined. + // Otherwise, backward flow will lead to premature deletion: + // 1: 0 > 5 + // move( call_tmp call( fn newAlias arg this ) ) + // move( ret call_tmp ) + // goto 806806 _end_reindex + // 4: 2 3 > 5 + // move( call_tmp call( fn _newArray x ) ) + // move( ret call_tmp ) + // 5: 4 1 > + // def _end_reindex + // return( ret ) + // In blocks 1 and 4, the respective call_tmp becomes live and is aliased + // with ret. But only the first call_tmp is live at the end of block 1 and + // only the second call_tmp is live at the end of block 4: they will not flow + // through to block 5. After flow analysis (unless we treat the members of a + // clique equivalently), each call_tmp will be destroyed within its + // respective block, making ret unowned on exit -- an error. + // TODO: Compute aliases in a separate traversal, so it is obvious that + // aliases are aliases across the entire function. + // TODO: I think we can represent all members of an alias set with one bit in + // the flow sets -- either all are owned or none are. This will save time + // and space. + SymbolVector* aliasList = aliases.at(sym); + for_vector(Symbol, alias, *aliasList) + { + size_t index = symbolIndex[alias]; + gen->set(index); + } } static void processBitwiseCopy(SymExpr* se, BitVec* gen, + const AliasVectorMap& aliases, SymbolIndexMap& symbolIndex) { CallExpr* call = toCallExpr(se->parentExpr); @@ -629,11 +732,8 @@ static void processBitwiseCopy(SymExpr* se, BitVec* gen, Symbol* lsym = lhs->var; Symbol* rsym = rhs->var; - size_t lindex = symbolIndex[lsym]; - size_t rindex = symbolIndex[rsym]; - // Copy ownership state from RHS. - INT_ASSERT(gen->get(lindex) == false); + size_t rindex = symbolIndex[rsym]; if (!gen->get(rindex)) { if (fWarnOwnership) @@ -652,26 +752,16 @@ static void processBitwiseCopy(SymExpr* se, BitVec* gen, } else { - gen->set(lindex); + SymbolVector* aliasList = aliases.at(lsym); + for_vector(Symbol, alias, *aliasList) + { + size_t index = symbolIndex[alias]; + gen->set(index); + } } } -static void createAlias(SymExpr* se, AliasVectorMap& aliases) -{ - CallExpr* call = toCallExpr(se->parentExpr); - - SymExpr* lhs = toSymExpr(call->get(1)); - SymExpr* rhs = toSymExpr(call->get(2)); - - Symbol* lsym = lhs->var; - Symbol* rsym = rhs->var; - - // Merge aliases whether or not they are live. - aliases.merge(rsym, lsym); -} - - // If this call acts like a destructor, then add the symbols it affects to the // kill set and remove them from the gen set. static void processConsumer(SymExpr* se, BitVec* gen, BitVec* kill, @@ -685,7 +775,6 @@ static void processConsumer(SymExpr* se, BitVec* gen, BitVec* kill, // need to look up one arbitrarily and then run the list. Symbol* sym = se->var; SymbolVector* aliasList = aliases.at(sym); - for_vector(Symbol, alias, *aliasList) { size_t index = symbolIndex[alias]; @@ -709,13 +798,10 @@ static void computeTransitions(SymExprVector& symExprs, continue; if (isCreated(se)) - processCreator(se, gen, symbolIndex); + processCreator(se, gen, aliases, symbolIndex); if (isBitwiseCopy(se, symbolIndex)) - { - processBitwiseCopy(se, gen, symbolIndex); - createAlias(se, aliases); - } + processBitwiseCopy(se, gen, aliases, symbolIndex); if (isConsumed(se)) processConsumer(se, gen, kill, aliases, symbolIndex); @@ -765,14 +851,18 @@ static bool isRetVarInReturn(SymExpr* se) if (call->isPrimitive(PRIM_RETURN)) // We just assume that that call->get(1) == se. // What else could it be? - return true; + if (FnSymbol* fn = toFnSymbol(call->parentSymbol)) + if (fn->hasFlag(FLAG_CONSTRUCTOR) || + fn->hasFlag(FLAG_AUTO_COPY_FN) || + fn->hasFlag(FLAG_INIT_COPY_FN)) + return true; return false; } // Insert an autoCopy because this symbol is unowned and -// a receptor formal expects is operand to be owned. +// a receptor formal expects its operand to be owned. static void insertAutoCopy(SymExpr* se) { SET_LINENO(se); @@ -812,11 +902,11 @@ static void insertAutoCopy(SymExprVector& symExprs, BitVec* gen, BitVec* kill, // Set a bit in the gen set if this is a constructor. if (isCreated(se)) - processCreator(se, gen, symbolIndex); + processCreator(se, gen, aliases, symbolIndex); // Pass ownership to this symbol if it is the result of a bitwise copy. if (isBitwiseCopy(se, symbolIndex)) - processBitwiseCopy(se, gen, symbolIndex); + processBitwiseCopy(se, gen, aliases, symbolIndex); if (isConsumed(se)) { @@ -919,10 +1009,12 @@ static void insertAutoDestroy(BasicBlock* bb, BitVec* to_kill, // autoDestroy call for it. continue; +#if 0 // For now, we ignore internally reference-counted types. We'll add the // code to manage those later. if (isRefCountedType(sym->type)) continue; +#endif CallExpr* autoDestroyCall = new CallExpr(autoDestroy, sym); @@ -988,14 +1080,6 @@ static void backwardFlowOwnership(const BasicBlock::BasicBlockVector& bbs, static void insertAutoCopyAutoDestroy(FnSymbol* fn) { BasicBlock::buildBasicBlocks(fn); - size_t nbbs = fn->basicBlocks->size(); - - SymbolVector symbols; - SymbolIndexMap symbolIndex; - AliasVectorMap aliases; - extractSymbols(fn, symbols, symbolIndex, aliases); - - size_t size = symbols.size(); #if DEBUG_AMM if (debug > 0) @@ -1007,6 +1091,14 @@ static void insertAutoCopyAutoDestroy(FnSymbol* fn) } #endif + SymbolVector symbols; + SymbolIndexMap symbolIndex; + AliasVectorMap aliases; + extractSymbols(fn, symbols, aliases, symbolIndex); + populateAliases(fn, symbols, aliases, symbolIndex); + + size_t nbbs = fn->basicBlocks->size(); + size_t size = symbols.size(); FlowSet GEN; FlowSet KILL; FlowSet IN; diff --git a/test/classes/diten/record_initialize.good b/test/classes/diten/record_initialize.good index 77897d4ced0a..5608bc256725 100644 --- a/test/classes/diten/record_initialize.good +++ b/test/classes/diten/record_initialize.good @@ -1,6 +1,5 @@ BBB.initialize() BBB.initialize() -BBB.initialize() AAA.initialize() BBB.initialize() BBB.initialize()