<commit_before>/*
 * Copyright 2004-2015 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.
 */

//
// Transformations for begin, cobegin, and on statements
//

#include "passes.h"

#include "astutil.h"
#include "driver.h"
#include "expr.h"
#include "files.h"
#include "optimizations.h"
#include "resolution.h"
#include "stlUtil.h"
#include "stmt.h"
#include "stringutil.h"
#include "symbol.h"
#include "CForLoop.h"

// Notes on
//   makeHeapAllocations()    //invoked from parallel()
//   insertWideReferences()
//
//------
// Terminology/abbreviations:
//
// 'on'+'begin' is considered to be both an 'on' and a 'begin'
// - see also FLAG_NON_BLOCKING
//
// A "global" is a module-level symbol, usu. a VarSymbol.
// A 'var' is a global if, equivalently:
//  isModuleSymbol(var->defPoint->parentSymbol)
//  isGlobal(var)
//
// MHA = makeHeapAllocations() and functions it invokes
// IWR = insertWideReferences() and functions it invokes
//------
//
// MHA and IWR take care of the following, among others:
// - heap allocation for remote access
// - heap allocation for 'begin'
// - change acces to variable -> access to its ._value
// - set up wide references
// - broadcasting of globals
//
// In more details:
//
// Heap allocation for remote access is done:
// - for globals - in IWR, if:
//    requireWideReferences()
// - for a local - in MHA, if:
//    needHeapVars() && the local can be passed to an 'on'
//
// Heap allocation for 'begin' is done:
// - for globals - n/a
//    see above instead
// - for a local - in MHA, if:
//    the local can be passed to a 'begin'
//
// Change acces to variable -> access to its ._value
// - for globals - in MHA, if:
//    requireWideReferences()
// - for locals - in MHA, if:
//    the local is subject to heap allocation
//    in either of the above two categories
//
// Wide references are set up in IWR, if:
//    requireWideReferences()
//
// Broadcasting:
// - of certain global constants       - in MHA/findHeapVarsAndRefs, if:
//    !fLocal
// - of global arrays/domains/distribs - in MHA/findHeapVarsAndRefs, if:
//    !fLocal
// - of locations of the other globals - in IWR, if:
//    requireWideReferences()


typedef struct {
  bool firstCall;
  AggregateType* ctype;
  FnSymbol*  wrap_fn;
} BundleArgsFnData;

// bundleArgsFnDataInit: the initial value for BundleArgsFnData
static BundleArgsFnData bundleArgsFnDataInit = { true, NULL, NULL };

static void insertEndCounts();
static void passArgsToNestedFns(Vec<FnSymbol*>& nestedFunctions);
static void create_block_fn_wrapper(FnSymbol* fn, CallExpr* fcall, BundleArgsFnData &baData);
static void call_block_fn_wrapper(FnSymbol* fn, CallExpr* fcall, VarSymbol* tempc, FnSymbol *wrap_fn);
static void findBlockRefActuals(Vec<Symbol*>& refSet, Vec<Symbol*>& refVec);
static void findHeapVarsAndRefs(Map<Symbol*,Vec<SymExpr*>*>& defMap,
                                Vec<Symbol*>& refSet, Vec<Symbol*>& refVec,
                                Vec<Symbol*>& varSet, Vec<Symbol*>& varVec);

// Package args into a class and call a wrapper function with that
// object. The wrapper function will then call the function
// created by the previous parallel pass. This is a way to pass along
// multiple args through the limitation of one arg in the runtime's
// thread creation interface. 
//
// Implemented using BundleArgsFnData and the functions:
//   create_arg_bundle_class
//   bundleArgs
//   create_block_fn_wrapper
//   call_block_fn_wrapper

// Even though the arg bundle class depends only on the iterator,
// current code unfortunately uses the call site for some information
// If there are multiple call sites, the first one is used.

static void create_arg_bundle_class(FnSymbol* fn, CallExpr* fcall, ModuleSymbol* mod, BundleArgsFnData &baData) {
  INT_ASSERT(!baData.ctype);
  SET_LINENO(fn);

// Here, 'fcall' is the first of fn's callees and so it acts as a
// representative of all the other callees, if any.
// As of this writing, this should be OK because the callees are
// obtained by duplicating the original call, which resulted in
// outlining a block into 'fn' and so is unique.
// To eliminate 'fcall' in create_arg_bundle_class(), we need
// to rely on fn's formal types instead of fcall's actual types.

  // create a new class to capture refs to locals
  AggregateType* ctype = new AggregateType(AGGREGATE_CLASS);
  TypeSymbol* new_c = new TypeSymbol(astr("_class_locals", fn->name), ctype);
  new_c->addFlag(FLAG_NO_OBJECT);
  new_c->addFlag(FLAG_NO_WIDE_CLASS);

  // add the function args as fields in the class
  int i = 0;    // Fields are numbered for uniqueness.
  for_actuals(arg, fcall) {
    SymExpr *s = toSymExpr(arg);
    Symbol  *var = s->var; // arg or var
    if (var->type->symbol->hasFlag(FLAG_REF) || isClass(var->type))
      // Only a variable that is passed by reference out of its current scope
      // is concurrently accessed -- which means that it has to be passed by
      // reference.
      var->addFlag(FLAG_CONCURRENTLY_ACCESSED);
    VarSymbol* field = new VarSymbol(astr("_", istr(i), "_", var->name), var->type);
    ctype->fields.insertAtTail(new DefExpr(field));
    i++;
  }
  // BTW 'mod' may differ from fn->defPoint->getModule()
  // e.g. due to iterator inlining.
  mod->block->insertAtHead(new DefExpr(new_c));

  baData.ctype = ctype;
}


/// Optionally autoCopies an argument being inserted into an argument bundle.
///
/// This routine optionally inserts an autoCopy ahead of each invocation of a
/// task function that begins asynchronous execution (currently just "begin" and
/// "nonblocking on" functions).  
/// If such an autoCopy call is inserted, a matching autoDestroy call is placed
/// at the end of the tasking routine before the call to _downEndCount.  Since a
/// tasking function may be called from several call sites, the task function is
/// modified only when processing the first invocation.
/// The insertion of autoCopy calls is required for internally reference-counted
/// types, and also for all user-defined record types (passed by value).  For
/// internally reference-counted types, the autoCopy call increases the
/// reference count, so the internal (reference-counted) data is not reclaimed
/// before the task function exits.  For user-defined record types, the autoCopy
/// call provides a hook so the record author can ensure that the task function
/// owns its own copy of the record (including, but not limited to,
/// reference-counting it).
/// \param firstCall Should be set to \p true for the first invocation of a
/// given task function and \p false thereafter.
/// \ret Returns the result of calling autoCopy on the given arg, if necessary;
/// otherwise, just returns 
static Symbol* insertAutoCopyDestroyForTaskArg
  (Expr* arg, ///< The actual argument being passed.
   CallExpr* fcall, ///< The call that invokes the task function.
   FnSymbol* fn, ///< The task function.
   bool firstCall)
{
  SymExpr* s = toSymExpr(arg);
  Symbol* var = s->var;

  // This applies only to arguments being passed to asynchronous task functions.
  // No need to increment+decrement the reference counters for cobegins/coforalls.
  if (fn->hasFlag(FLAG_BEGIN))
  {
    Type* baseType = arg->getValType();
    FnSymbol* autoCopyFn = getAutoCopy(baseType);
    FnSymbol* autoDestroyFn = getAutoDestroy(baseType);

    if (isRefCountedType(baseType))
    {
      // TODO: Can we consolidate these two clauses?
      // Does arg->typeInfo() != baseType mean that arg is passed by ref?
      if (arg->typeInfo() != baseType)
      {
        // For internally reference-counted types, this punches through
        // references to bump the reference count.
        VarSymbol* derefTmp = newTemp(baseType);
        fcall->insertBefore(new DefExpr(derefTmp));
        fcall->insertBefore(new CallExpr(PRIM_MOVE, derefTmp,
                                         new CallExpr(PRIM_DEREF, var)));
        // The result of the autoCopy call is dropped on the floor.
        // It is only called to increment the ref count.
        CallExpr* autoCopyCall = new CallExpr(autoCopyFn, derefTmp);
        fcall->insertBefore(autoCopyCall);
        insertReferenceTemps(autoCopyCall);
        // But the original var is passed through to the field assignment.
      }
      else
      {
        VarSymbol* valTmp = newTemp(baseType);
        valTmp->addFlag(FLAG_NECESSARY_AUTO_COPY);
        fcall->insertBefore(new DefExpr(valTmp));
        CallExpr* autoCopyCall = new CallExpr(autoCopyFn, var);
        fcall->insertBefore(new CallExpr(PRIM_MOVE, valTmp, autoCopyCall));
        insertReferenceTemps(autoCopyCall);
        // If the arg is not passed by reference, the result of the autoCopy is
        // passed to the field assignment.
        var = valTmp;
      }

      if (firstCall)
      {
        // The task function may be called from several call sites, so insert
        // the autodestroy call only once (when processing the first fcall).
        Symbol* formal = actual_to_formal(arg);
        if (arg->typeInfo() != baseType)
        {
          VarSymbol* derefTmp = newTemp(baseType);
          fn->insertBeforeDownEndCount(new DefExpr(derefTmp));
          fn->insertBeforeDownEndCount(
            new CallExpr(PRIM_MOVE, derefTmp, new CallExpr(PRIM_DEREF,  formal)));
          formal = derefTmp;
        }
        CallExpr* autoDestroyCall = new CallExpr(autoDestroyFn, formal);
        fn->insertBeforeDownEndCount(autoDestroyCall);
        insertReferenceTemps(autoDestroyCall);
      }
    }
    else if (isRecord(baseType))
    {
      // Do this only if the record is passed by value.
      if (arg->typeInfo() == baseType)
      {
        // TODO: Find out why _RuntimeTypeInfo records do not have autoCopy
        // functions, so we can get rid of this special test.
        if (autoCopyFn == NULL) return var;

        // Insert a call to the autoCopy function ahead of the call.
        VarSymbol* valTmp = newTemp(baseType);
        fcall->insertBefore(new DefExpr(valTmp));
        CallExpr* autoCopyCall = new CallExpr(autoCopyFn, var);
        fcall->insertBefore(new CallExpr(PRIM_MOVE, valTmp, autoCopyCall));
        insertReferenceTemps(autoCopyCall);
        var = valTmp;

        if (firstCall)
        {
          // Insert a call to the autoDestroy function ahead of the return.
          // (But only once per function for each affected argument.)
          Symbol* formal = actual_to_formal(arg);
          CallExpr* autoDestroyCall = new CallExpr(autoDestroyFn,formal);
          fn->insertBeforeDownEndCount(autoDestroyCall);
          insertReferenceTemps(autoDestroyCall);
        }
      }
    }
  }
  return var;
}


static void
bundleArgs(CallExpr* fcall, BundleArgsFnData &baData) {
  SET_LINENO(fcall);
  ModuleSymbol* mod = fcall->getModule();
  FnSymbol* fn = fcall->isResolved();

  const bool firstCall = baData.firstCall;
  if (firstCall)<commit_msg>Modify insertAutoCopyDestroyForTaskArg() to also insert autocopy calls when calling a cobegin function.<commit_after>/*
 * Copyright 2004-2015 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.
 */

//
// Transformations for begin, cobegin, and on statements
//

#include "passes.h"

#include "astutil.h"
#include "driver.h"
#include "expr.h"
#include "files.h"
#include "optimizations.h"
#include "resolution.h"
#include "stlUtil.h"
#include "stmt.h"
#include "stringutil.h"
#include "symbol.h"
#include "CForLoop.h"

// Notes on
//   makeHeapAllocations()    //invoked from parallel()
//   insertWideReferences()
//
//------
// Terminology/abbreviations:
//
// 'on'+'begin' is considered to be both an 'on' and a 'begin'
// - see also FLAG_NON_BLOCKING
//
// A "global" is a module-level symbol, usu. a VarSymbol.
// A 'var' is a global if, equivalently:
//  isModuleSymbol(var->defPoint->parentSymbol)
//  isGlobal(var)
//
// MHA = makeHeapAllocations() and functions it invokes
// IWR = insertWideReferences() and functions it invokes
//------
//
// MHA and IWR take care of the following, among others:
// - heap allocation for remote access
// - heap allocation for 'begin'
// - change acces to variable -> access to its ._value
// - set up wide references
// - broadcasting of globals
//
// In more details:
//
// Heap allocation for remote access is done:
// - for globals - in IWR, if:
//    requireWideReferences()
// - for a local - in MHA, if:
//    needHeapVars() && the local can be passed to an 'on'
//
// Heap allocation for 'begin' is done:
// - for globals - n/a
//    see above instead
// - for a local - in MHA, if:
//    the local can be passed to a 'begin'
//
// Change acces to variable -> access to its ._value
// - for globals - in MHA, if:
//    requireWideReferences()
// - for locals - in MHA, if:
//    the local is subject to heap allocation
//    in either of the above two categories
//
// Wide references are set up in IWR, if:
//    requireWideReferences()
//
// Broadcasting:
// - of certain global constants       - in MHA/findHeapVarsAndRefs, if:
//    !fLocal
// - of global arrays/domains/distribs - in MHA/findHeapVarsAndRefs, if:
//    !fLocal
// - of locations of the other globals - in IWR, if:
//    requireWideReferences()


typedef struct {
  bool firstCall;
  AggregateType* ctype;
  FnSymbol*  wrap_fn;
} BundleArgsFnData;

// bundleArgsFnDataInit: the initial value for BundleArgsFnData
static BundleArgsFnData bundleArgsFnDataInit = { true, NULL, NULL };

static void insertEndCounts();
static void passArgsToNestedFns(Vec<FnSymbol*>& nestedFunctions);
static void create_block_fn_wrapper(FnSymbol* fn, CallExpr* fcall, BundleArgsFnData &baData);
static void call_block_fn_wrapper(FnSymbol* fn, CallExpr* fcall, VarSymbol* tempc, FnSymbol *wrap_fn);
static void findBlockRefActuals(Vec<Symbol*>& refSet, Vec<Symbol*>& refVec);
static void findHeapVarsAndRefs(Map<Symbol*,Vec<SymExpr*>*>& defMap,
                                Vec<Symbol*>& refSet, Vec<Symbol*>& refVec,
                                Vec<Symbol*>& varSet, Vec<Symbol*>& varVec);

// Package args into a class and call a wrapper function with that
// object. The wrapper function will then call the function
// created by the previous parallel pass. This is a way to pass along
// multiple args through the limitation of one arg in the runtime's
// thread creation interface. 
//
// Implemented using BundleArgsFnData and the functions:
//   create_arg_bundle_class
//   bundleArgs
//   create_block_fn_wrapper
//   call_block_fn_wrapper

// Even though the arg bundle class depends only on the iterator,
// current code unfortunately uses the call site for some information
// If there are multiple call sites, the first one is used.

static void create_arg_bundle_class(FnSymbol* fn, CallExpr* fcall, ModuleSymbol* mod, BundleArgsFnData &baData) {
  INT_ASSERT(!baData.ctype);
  SET_LINENO(fn);

// Here, 'fcall' is the first of fn's callees and so it acts as a
// representative of all the other callees, if any.
// As of this writing, this should be OK because the callees are
// obtained by duplicating the original call, which resulted in
// outlining a block into 'fn' and so is unique.
// To eliminate 'fcall' in create_arg_bundle_class(), we need
// to rely on fn's formal types instead of fcall's actual types.

  // create a new class to capture refs to locals
  AggregateType* ctype = new AggregateType(AGGREGATE_CLASS);
  TypeSymbol* new_c = new TypeSymbol(astr("_class_locals", fn->name), ctype);
  new_c->addFlag(FLAG_NO_OBJECT);
  new_c->addFlag(FLAG_NO_WIDE_CLASS);

  // add the function args as fields in the class
  int i = 0;    // Fields are numbered for uniqueness.
  for_actuals(arg, fcall) {
    SymExpr *s = toSymExpr(arg);
    Symbol  *var = s->var; // arg or var
    if (var->type->symbol->hasFlag(FLAG_REF) || isClass(var->type))
      // Only a variable that is passed by reference out of its current scope
      // is concurrently accessed -- which means that it has to be passed by
      // reference.
      var->addFlag(FLAG_CONCURRENTLY_ACCESSED);
    VarSymbol* field = new VarSymbol(astr("_", istr(i), "_", var->name), var->type);
    ctype->fields.insertAtTail(new DefExpr(field));
    i++;
  }
  // BTW 'mod' may differ from fn->defPoint->getModule()
  // e.g. due to iterator inlining.
  mod->block->insertAtHead(new DefExpr(new_c));

  baData.ctype = ctype;
}


/// Optionally autoCopies an argument being inserted into an argument bundle.
///
/// This routine optionally inserts an autoCopy ahead of each invocation of a
/// task function that begins asynchronous execution (currently just "begin" and
/// "nonblocking on" functions).  
/// If such an autoCopy call is inserted, a matching autoDestroy call is placed
/// at the end of the tasking routine before the call to _downEndCount.  Since a
/// tasking function may be called from several call sites, the task function is
/// modified only when processing the first invocation.
/// The insertion of autoCopy calls is required for internally reference-counted
/// types, and also for all user-defined record types (passed by value).  For
/// internally reference-counted types, the autoCopy call increases the
/// reference count, so the internal (reference-counted) data is not reclaimed
/// before the task function exits.  For user-defined record types, the autoCopy
/// call provides a hook so the record author can ensure that the task function
/// owns its own copy of the record (including, but not limited to,
/// reference-counting it).
/// \param firstCall Should be set to \p true for the first invocation of a
/// given task function and \p false thereafter.
/// \ret Returns the result of calling autoCopy on the given arg, if necessary;
/// otherwise, just returns 
static Symbol* insertAutoCopyDestroyForTaskArg
  (Expr* arg, ///< The actual argument being passed.
   CallExpr* fcall, ///< The call that invokes the task function.
   FnSymbol* fn, ///< The task function.
   bool firstCall)
{
  SymExpr* s = toSymExpr(arg);
  Symbol* var = s->var;

  // This applies only to arguments being passed to asynchronous task functions.
  // No need to increment+decrement the reference counters for
  // cobegins/coforalls.
#if HILDE_MM
  // The following comment is not true, because it assumes that reference counting
  // is the only way in which user memory management can be done.  In the
  // single-owner model, a verbatim copy is obligatory.  Otherwise, the
  // original may disappear before control ever reaches the task function.
  // So, sorry.  We have to do this for all task functions, not merely
  // asynchronous begins.
#else
  if (fn->hasFlag(FLAG_BEGIN))
  {
#endif
    Type* baseType = arg->getValType();
    FnSymbol* autoCopyFn = getAutoCopy(baseType);
    FnSymbol* autoDestroyFn = getAutoDestroy(baseType);

    if (isRefCountedType(baseType))
    {
      // TODO: Can we consolidate these two clauses?
      // Does arg->typeInfo() != baseType mean that arg is passed by ref?
      if (arg->typeInfo() != baseType)
      {
        // For internally reference-counted types, this punches through
        // references to bump the reference count.
        VarSymbol* derefTmp = newTemp(baseType);
        fcall->insertBefore(new DefExpr(derefTmp));
        fcall->insertBefore(new CallExpr(PRIM_MOVE, derefTmp,
                                         new CallExpr(PRIM_DEREF, var)));
        // The result of the autoCopy call is dropped on the floor.
        // It is only called to increment the ref count.
        CallExpr* autoCopyCall = new CallExpr(autoCopyFn, derefTmp);
        fcall->insertBefore(autoCopyCall);
        insertReferenceTemps(autoCopyCall);
        // But the original var is passed through to the field assignment.
      }
      else
      {
        VarSymbol* valTmp = newTemp(baseType);
        valTmp->addFlag(FLAG_NECESSARY_AUTO_COPY);
        fcall->insertBefore(new DefExpr(valTmp));
        CallExpr* autoCopyCall = new CallExpr(autoCopyFn, var);
        fcall->insertBefore(new CallExpr(PRIM_MOVE, valTmp, autoCopyCall));
        insertReferenceTemps(autoCopyCall);
        // If the arg is not passed by reference, the result of the autoCopy is
        // passed to the field assignment.
        var = valTmp;
      }

      if (firstCall)
      {
        // The task function may be called from several call sites, so insert
        // the autodestroy call only once (when processing the first fcall).
        Symbol* formal = actual_to_formal(arg);
        if (arg->typeInfo() != baseType)
        {
          VarSymbol* derefTmp = newTemp(baseType);
          fn->insertBeforeDownEndCount(new DefExpr(derefTmp));
          fn->insertBeforeDownEndCount(
            new CallExpr(PRIM_MOVE, derefTmp, new CallExpr(PRIM_DEREF,  formal)));
          formal = derefTmp;
        }
        CallExpr* autoDestroyCall = new CallExpr(autoDestroyFn, formal);
        fn->insertBeforeDownEndCount(autoDestroyCall);
        insertReferenceTemps(autoDestroyCall);
      }
    }
    else if (isRecord(baseType))
    {
      // Do this only if the record is passed by value.
      if (arg->typeInfo() == baseType)
      {
        // TODO: Find out why _RuntimeTypeInfo records do not have autoCopy
        // functions, so we can get rid of this special test.
        if (autoCopyFn == NULL) return var;

        // Insert a call to the autoCopy function ahead of the call.
        VarSymbol* valTmp = newTemp(baseType);
        fcall->insertBefore(new DefExpr(valTmp));
        CallExpr* autoCopyCall = new CallExpr(autoCopyFn, var);
        fcall->insertBefore(new CallExpr(PRIM_MOVE, valTmp, autoCopyCall));
        insertReferenceTemps(autoCopyCall);
        var = valTmp;

        if (firstCall)
        {
          // Insert a call to the autoDestroy function ahead of the return.
          // (But only once per function for each affected argument.)
          Symbol* formal = actual_to_formal(arg);
          CallExpr* autoDestroyCall = new CallExpr(autoDestroyFn,formal);
          fn->insertBeforeDownEndCount(autoDestroyCall);
          insertReferenceTemps(autoDestroyCall);
        }
      }
#if !HILDE_MM
    }
#endif
  }
  return var;
}


static void
bundleArgs(CallExpr* fcall, BundleArgsFnData &baData) {
  SET_LINENO(fcall);
  ModuleSymbol* mod = fcall->getModule();
  FnSymbol* fn = fcall->isResolved();

  const bool firstCall = baData.firstCall;
  if (firstCall)
{'fraction_non_alphanumeric': 0.10061453697817334, 'fraction_numerical': 0.0011019283746556473, 'mean_word_length': 3.0376454483230666, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 103, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5296678', 'n_tokens_mistral': 6952, 'n_tokens_neox': 6774, 'n_words': 2545}