/*
 * Copyright (c) 2017, NVIDIA CORPORATION.  All rights reserved.
 *
 * 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.
 *
 */

/**
   \file
   \brief Invariant communication optimization.
 */

#include "gbldefs.h"
#include "global.h"
#include "error.h"
#include "symtab.h"
#include "ast.h"
#include "nme.h"
#include "machar.h"
#include "gramtk.h"
#include "optimize.h"
#include "induc.h"
#include "extern.h"
#include "commopt.h"
#include "ccffinfo.h"
#include "direct.h"

/* Compiler switches:
    -x 49 0x20000:	Inhibit invariant communication hoisting
    -q 43 2:		Trace invariant communication hoisting
    -q 43 64:		Dump statements before and after hoisting
 */

/* local macros */
#define A_CONDG(a) A_OPT2G(a)
#define A_CONDP(a, v) A_OPT2P(a, v)

#if DEBUG
#define TRACE0(s)    \
  if (DBGBIT(43, 2)) \
  fprintf(gbl.dbgfil, s)
#define TRACE1(s, a1) \
  if (DBGBIT(43, 2))  \
  fprintf(gbl.dbgfil, s, a1)
#define TRACE2(s, a1, a2) \
  if (DBGBIT(43, 2))      \
  fprintf(gbl.dbgfil, s, a1, a2)
#define TRACE3(s, a1, a2, a3) \
  if (DBGBIT(43, 2))          \
  fprintf(gbl.dbgfil, s, a1, a2, a3)
#define TRACE4(s, a1, a2, a3, a4) \
  if (DBGBIT(43, 2))              \
  fprintf(gbl.dbgfil, s, a1, a2, a3, a4)
#define TRACE5(s, a1, a2, a3, a4, a5) \
  if (DBGBIT(43, 2))                  \
  fprintf(gbl.dbgfil, s, a1, a2, a3, a4, a5)
#else
#define TRACE0(s)
#define TRACE1(s, a1)
#define TRACE2(s, a1, a2)
#define TRACE3(s, a1, a2, a3)
#define TRACE4(s, a1, a2, a3, a4)
#define TRACE5(s, a1, a2, a3, a4, a5)
#endif

/* local functions */
static LOGICAL hoist_loop(int lp);
static void clean_flags(void);
static void get_loopcounts(void);
static LOGICAL contains_alloc(int lp);
static LOGICAL unique_allo(int allo_std, int allo_ft, int lp);
static void hoist_comms(int lp);
static void hoist_std(int, int, int);
static LOGICAL check_outer_defs(int std, int lp, int nme);
static LOGICAL check_inner_uses(int std, int lp, int nme);
static LOGICAL is_invariant_base(int nme);
static LOGICAL is_parent_loop(int lpParent, int lpChild);
static LOGICAL has_invar_subs(int ast);
static LOGICAL is_invariant_list(int n, int vast[]);
static LOGICAL check_moved_list(int n, int vstd[]);
static LOGICAL check_moved(int std);
static void move_invar_std(int std, int fg);
static void mk_loop_hdtl(int lp);
static int mk_count_test(int astCond, int astCount);
static int mk_dealloc_stub(int astCond);
static void mk_ztrips(int lp);
static int remove_cond(int astTarg, int astSrc);

/* global data */
static int fgPre;              /* pre-header of current loop */
static int fgPost;             /* trailer of current loop */
static LOGICAL bMovedComm;     /* TRUE when invariant comm statement moved */
static LOGICAL bMovedAssn;     /* TRUE when invariant assignment moved */
static int *pastCounts = NULL; /* loop counts */

/** \brief Hoist invariant communication expressions out of loops. */
void
comm_invar(void)
{
  int lp;

  hlopt_init(HLOPT_INDUC);

#if DEBUG
  if (DBGBIT(43, 64)) {
    fprintf(gbl.dbgfil, "----------- STDs before invariant hoisting\n");
    dump_std();
  }
#endif

  get_loopcounts();

  clean_flags();

  for (lp = LP_CHILD(0); lp; lp = LP_SIBLING(lp))
    hoist_loop(lp);

  mk_ztrips(0);
#if DEBUG
  if (DBGBIT(43, 64)) {
    fprintf(gbl.dbgfil, "----------- STDs after invariant hoisting\n");
    dump_std();
  }
#endif

  if (pastCounts) {
    FREE(pastCounts);
    pastCounts = NULL;
  }
  hlopt_end(HLOPT_INDUC, 0);
}

/* Clear flags & fields in STDs & ASTs that will be used for hoisting. */
static void
clean_flags(void)
{
  int std;

  for (std = STD_FIRST; std; std = STD_NEXT(std)) {
    STD_MOVED(std) = FALSE;
    A_CONDP(STD_AST(std), 0);
  }
}

/* For each loop lp, set pastCounts[lp] to its count. */
static void
get_loopcounts(void)
{
  int lp;

  NEW(pastCounts, int, opt.nloops + 1);

  for (lp = 1; lp <= opt.nloops; lp++) {
    /* Mark invariant ASTs within lp. */
    invariant_nomotion(lp);
    pastCounts[lp] = get_loop_count(lp);
    end_loop_count();
    invariant_unmarkv();
  }
}

/* Hoist invariant communication expressions out of loop #lp. Return TRUE
 * if lp is a candidate for hoisting. */
static LOGICAL
hoist_loop(int lp)
{
  int lpi;
  LOGICAL bHoistInner = TRUE;
  LOGICAL bx49;

  for (lpi = LP_CHILD(lp); lpi; lpi = LP_SIBLING(lpi))
    bHoistInner &= hoist_loop(lpi);

  if (!bHoistInner)
    /* Don't analyze if an inner loop can't be analyzed. */
    return FALSE;
  if (LP_MEXITS(lp))
    /* Don't analyze if lp contains multiple exits. */
    return TRUE;
  if (!pastCounts[lp])
    /* Loop not countable. */
    return TRUE;
  if (contains_alloc(lp))
    /* Don't analyze if user-defined arrays are allocated. */
    return FALSE;

  /* Check for a disabling directive. */
  open_pragma(FG_LINENO(LP_HEAD(lp)));
  bx49 = (XBIT(49, 0x20000) != 0);
  close_pragma();
  if (bx49)
    return TRUE;

  /* Mark invariant ASTs within lp. */
  invariant_nomotion(lp);

  fgPre = fgPost = 0;

  /* Hoist communication ASTs. */
  hoist_comms(lp);

  invariant_unmarkv();
  return TRUE;
}

/* Return TRUE if loop #lp contains an ALLOCATE/DEALLOCATE statement of
 * a user-defined array. */
static LOGICAL
contains_alloc(int lp)
{
  int fg;
  int std, stdend;
  int ast, sptr;

  for (fg = LP_FG(lp); fg; fg = FG_NEXT(fg)) {
    std = FG_STDFIRST(fg);
    if (!std)
      continue;
    stdend = STD_NEXT(FG_STDLAST(fg));
    for (; std != stdend; std = STD_NEXT(std)) {
      ast = STD_AST(std);
      if (A_TYPEG(ast) != A_ALLOC)
        continue;
      ast = A_SRCG(ast);
      if (A_TYPEG(ast) == A_SUBSCR)
        ast = A_LOPG(ast);
      switch (A_TYPEG(ast)) {
      case A_ID:
        sptr = A_SPTRG(ast);
        break;
      case A_MEM:
        sptr = A_SPTRG(A_MEMG(ast));
        break;
      default:
        interr("contains_alloc: ID operand not found", lp, 4);
      }
      if (!CCSYMG(sptr))
        return TRUE;
    }
  }
  return FALSE;
}

/* Hoist invariant communication ASTs within loop #lp into a
 * pre-header (and trailer) located at fgPre (and fgPost). */
static void
hoist_comms(int lp)
{
  int fg, fgtail, fgend;
  int std, stdend, stdnext;
  int ast;
  int par;

  bMovedComm = FALSE;
  bMovedAssn = FALSE;
  fgtail = LP_TAIL(lp);
  fgend = FG_LNEXT(fgtail);
  fg = LP_HEAD(lp);
  par = STD_PAR(FG_STDFIRST(fg));
  for (; fg != fgend; fg = FG_LNEXT(fg)) {
    if (FG_LOOP(fg) != lp)
      continue;
    if (!is_dominator(fg, fgtail))
      continue;
    std = FG_STDFIRST(fg);
    if (!std)
      continue;
    stdend = STD_NEXT(FG_STDLAST(fg));
    stdnext = STD_NEXT(std);
    for (; std != stdend; std = stdnext, stdnext = STD_NEXT(stdnext)) {
      if (par != STD_PAR(std))
        continue;
      if (!STD_DELETE(std))
        hoist_std(fg, std, lp);
    }
  }

  if (!bMovedComm && !bMovedAssn)
    return;

  /* Clear the flags. */
  std = FG_STDFIRST(fgPre);
  if (std) {
    stdend = STD_NEXT(FG_STDLAST(fgPre));
    for (; std != stdend; std = STD_NEXT(std))
      STD_MOVED(std) = FALSE;
  }

  if (bMovedComm && flg.hpf)
    ccff_info(MSGFTN, "FTN012", 1, FG_LINENO(LP_HEAD(lp)),
              "Invariant communication calls hoisted out of loop", NULL);
  if (bMovedAssn)
    ccff_info(MSGFTN, "FTN013", 1, FG_LINENO(LP_HEAD(lp)),
              "Invariant assignments hoisted out of loop", NULL);
}

/* Return TRUE if the A_HALLOBNDS at allo_std is unique in lp.  That is,
   no other A_HALLOBNDS in the loop allocates/deallocates the same symbol. */
static LOGICAL
unique_allo(int allo_std, int allo_ft, int lp)
{
  int fg;
  int std, stdend;
  int ast, ft;

  for (fg = LP_FG(lp); fg; fg = FG_NEXT(fg)) {
    std = FG_STDFIRST(fg);
    if (!std)
      continue;
    stdend = STD_NEXT(FG_STDLAST(fg));
    for (; std != stdend; std = STD_NEXT(std)) {
      if (std == allo_std)
        continue;
      ast = STD_AST(std);
      if (A_TYPEG(ast) != A_HALLOBNDS)
        continue;
      ft = A_OPT1G(ast);
      if (!ft)
        continue;
      if (FT_ALLOC_SPTR(ft) == FT_ALLOC_SPTR(allo_ft))
        return FALSE;
    }
  }
  return TRUE;
}

/* Hoist an invariant communication AST at STD std to fgPre. STD std occurs
 * within node #fg. Set bMovedComm or bMovedAssn to TRUE when hoisting
 * occurs. */
static void
hoist_std(int fg, int std, int lp)
{
  int ast, ast1, astcomm, astcont;
  int ft;
  int stdfree;
  int nme;
  int ndim, dim;
  int asd;

  if (!std)
    return;

  ast = STD_AST(std);

  if (A_CONDG(ast) && !is_invariant(A_CONDG(ast)))
    return;

  if (A_TYPEG(ast) == A_ASN && A_TYPEG(A_DESTG(ast)) == A_ID
      && STYPEG(A_SPTRG(A_DESTG(ast))) == ST_VAR) {
    /* Hoist unique scalar assignments with invariant right-hand sides,
     * which may be allocatable array bounds. */
    nme = A_NMEG(A_DESTG(ast));
    if (is_sym_invariant_safe(nme, lp) &&
        check_outer_defs(std, FG_LOOP(fg), nme) &&
        check_inner_uses(std, FG_LOOP(fg), nme) && is_invariant(A_SRCG(ast)) &&
        is_invariant_base(nme)) {
      invariant_mark(A_DESTG(ast), INV);
      NME_STL(nme) = 0;
      move_invar_std(std, fg);
      bMovedAssn = TRUE;
      return;
    }
  }

  if (A_TYPEG(ast) == A_ASN)
    astcomm = A_SRCG(ast);
  else
    astcomm = ast;
  ft = A_OPT1G(astcomm);

  switch (A_TYPEG(astcomm)) {
  case A_HALLOBNDS:
    if (!ft || !unique_allo(std, ft, lp) || !has_invar_subs(A_LOPG(astcomm)))
      return;
    move_invar_std(std, fg);
    stdfree = mk_dealloc_stub(A_CONDG(ast));
    FT_ALLOC_FREE(ft) = stdfree;
    bMovedComm = TRUE;
    return;
  case A_HSECT:
    if (!ft || !has_invar_subs(A_LOPG(astcomm)) ||
        !check_moved(FT_SECT_ALLOC(ft)))
      return;
    move_invar_std(std, fg);
    stdfree = mk_dealloc_stub(A_CONDG(ast));
    FT_SECT_FREE(ft) = stdfree;
    bMovedComm = TRUE;
    return;
  case A_HLOCALIZEBNDS:
  case A_HCYCLICLP:
    if (!ft || !is_invariant(A_ITRIPLEG(astcomm)))
      return;
    move_invar_std(std, fg);
    bMovedComm = TRUE;
    return;
  case A_HCOPYSECT:
    if (!ft || !has_invar_subs(A_SRCG(astcomm)) ||
        !check_moved(FT_CCOPY_ALLOC(ft)) || !check_moved(FT_CCOPY_SECTL(ft)) ||
        !check_moved(FT_CCOPY_SECTR(ft)))
      return;
    move_invar_std(std, fg);
    stdfree = mk_dealloc_stub(A_CONDG(ast));
    FT_CCOPY_FREE(ft) = stdfree;
    bMovedComm = TRUE;
    return;
  case A_HCSTART:
    assert(A_TYPEG(A_SRCG(astcomm)) == A_SUBSCR,
           "hoist_std: source operand not subscript", ast, 4);
    assert(A_TYPEG(A_DESTG(astcomm)) == A_SUBSCR,
           "hoist_std: dest operand not subscript", ast, 4);
    if (!ft || !is_invariant(A_SRCG(astcomm)) ||
        !is_invariant(A_DESTG(astcomm)) || !check_moved(FT_CSTART_COMM(ft)) ||
        !check_moved(FT_CSTART_SECTL(ft)) || !check_moved(FT_CSTART_SECTR(ft)))
      return;
    move_invar_std(std, fg);
    stdfree = mk_dealloc_stub(A_CONDG(ast));
    FT_CSTART_FREE(ft) = stdfree;
    bMovedComm = TRUE;
    FT_CSTART_INVMVD(ft) = 1;
    return;
  case A_HOVLPSHIFT:
    if (!ft || !has_invar_subs(A_SRCG(astcomm)) ||
        (FT_SHIFT_BOUNDARY(ft) && !is_invariant(FT_SHIFT_BOUNDARY(ft))))
      return;
    move_invar_std(std, fg);
    stdfree = mk_dealloc_stub(A_CONDG(ast));
    FT_SHIFT_FREE(ft) = stdfree;
    bMovedComm = TRUE;
    return;
  case A_HSCATTER:
  case A_HGATHER:
    if (!ft)
      return;
    ndim = ASD_NDIM(A_ASDG(FT_CGATHER_VSUB(ft)));
    if (!has_invar_subs(FT_CGATHER_VSUB(ft)) ||
        !has_invar_subs(FT_CGATHER_NVSUB(ft)) ||
        (FT_CGATHER_MASK(ft) && !is_invariant(FT_CGATHER_MASK(ft))) ||
        !is_invariant_list(ndim, &FT_CGATHER_V(ft, 0)) ||
        !check_moved(FT_CGATHER_SECTVSUB(ft)) ||
        !check_moved(FT_CGATHER_SECTNVSUB(ft)) ||
        !check_moved(FT_CGATHER_SECTM(ft)) ||
        !check_moved_list(ndim, &FT_CGATHER_SECTV(ft, 0)))
      return;
    move_invar_std(std, fg);
    stdfree = mk_dealloc_stub(A_CONDG(ast));
    FT_CGATHER_FREE(ft) = stdfree;
    bMovedComm = TRUE;
    return;
  case A_HGETSCLR:
    if (!is_invariant(A_SRCG(astcomm)))
      return;
    move_invar_std(std, fg);
    bMovedComm = TRUE;
    return;
  case A_HOWNERPROC:
    ast1 = A_LOPG(astcomm);
    assert(A_TYPEG(ast1) == A_SUBSCR, "hoist_std: missing array ref", astcomm,
           4);
    asd = A_ASDG(ast1);
    dim = CONVAL2G(A_SPTRG(A_DIMG(astcomm)));
    if (!is_invariant(ASD_SUBS(asd, dim)))
      return;
    move_invar_std(std, fg);
    bMovedComm = TRUE;
    return;
  default:
    return;
  }
}

/* Return TRUE if every definition of nme that is not at std occurs in
 * a parent of loop lp. */
static LOGICAL
check_outer_defs(int std, int lp, int nme)
{
  int def;
  int lpo;

  for (def = NME_DEF(nme); def; def = DEF_NEXT(def)) {
    if (DEF_STD(def) == std)
      continue;
    if (is_parent_loop(lp, FG_LOOP(DEF_FG(def))))
      return FALSE;
  }
  return TRUE;
}

/* Return TRUE if every use of nme within loop lp is not reached by
 * a def of nme other than the def at STD std. */
static LOGICAL
check_inner_uses(int std, int lp, int nme)
{
  int def;
  DU *du;
  int use;

  for (def = NME_DEF(nme); def; def = DEF_NEXT(def)) {
    if (DEF_STD(def) == std)
      continue;
    for (du = DEF_DU(def); du; du = du->next) {
      use = du->use;
      if (is_parent_loop(lp, FG_LOOP(USE_FG(use))))
        return FALSE;
    }
  }
  return TRUE;
}

/*
 * return FALSE if this NME is for a BASED symbol, and the
 * base pointer (from MIDNUM) is not already marked invariant
 */
static LOGICAL
is_invariant_base(int nme)
{
  int sym;
  sym = NME_SYM(nme);
  if (SCG(sym) == SC_BASED && MIDNUMG(sym) > NOSYM &&
      !is_invariant(mk_id(MIDNUMG(sym))))
    return FALSE;
  return TRUE;
} /* is_invariant_base */

/* Return TRUE if lpParent is a parent loop of lpChild, or if they are
 * identical. */
static LOGICAL
is_parent_loop(int lpParent, int lpChild)
{
  int lpo;

  for (lpo = lpChild; lpo; lpo = LP_PARENT(lpo))
    if (lpo == lpParent)
      return TRUE;
  return FALSE;
}

/* Return TRUE if the subscript expressions of AST ast are all invariant. */
static LOGICAL
has_invar_subs(int ast)
{
  int asd;
  int ndims, sub;

  if (!ast)
    return TRUE;

  if (A_TYPEG(ast) == A_ID) {
    assert(A_SHAPEG(ast), "has_invar_subs: ast has no shape", ast, 4);
    return TRUE;
  }
  while (A_TYPEG(ast) != A_ID) {
    switch (A_TYPEG(ast)) {
    case A_SUBSCR:
      asd = A_ASDG(ast);
      ndims = ASD_NDIM(asd);
      for (sub = 0; sub < ndims; sub++) {
        if (!is_invariant(ASD_SUBS(asd, sub)))
          return FALSE;
      }
      ast = A_LOPG(ast);
      break;
    case A_MEM:
      ast = A_PARENTG(ast);
      break;
    default:
      interr("reference not ID,SUBSCR,MEM", ast, 4);
    }
  }
  return TRUE;
}

/* Return TRUE if every AST in vast[0], ..., vast[n-1] is invariant. */
static LOGICAL
is_invariant_list(int n, int vast[])
{
  int i;

  for (i = 0; i < n; i++)
    if (vast[i] && !is_invariant(vast[i]))
      return FALSE;
  return TRUE;
}

/* Return TRUE if every STD in vstd[0], ..., vstd[n-1] has been moved. */
static LOGICAL
check_moved_list(int n, int vstd[])
{
  int i;

  for (i = 0; i < n; i++)
    if (!check_moved(vstd[i]))
      return FALSE;
  return TRUE;
}

/* Return TRUE if std has been moved. */
static LOGICAL
check_moved(int std)
{
  if (!std)
    return TRUE;
  if (STD_DELETE(std))
    return FALSE;
  return STD_MOVED(std) != 0;
}

/* Move the invariant AST at STD #std within node #fg to the last statement
 * in the preheader of fg's loop. Set the std's MOVED flag. */
static void
move_invar_std(int std, int fg)
{
  int stdprev;
  int astCont, astStmt, astCond;

  mk_loop_hdtl(FG_LOOP(fg));

  rdilts(fg);
  stdprev = STD_PREV(std);
  if (STD_LABEL(std)) {
    astCont = mk_stmt(A_CONTINUE, 0);
    stdprev = add_stmt_after(astCont, stdprev);
    STD_LABEL(stdprev) = STD_LABEL(std);
    STD_LABEL(std) = 0;
    STD_LINENO(stdprev) = STD_LINENO(std);
  }
  STD_NEXT(stdprev) = STD_NEXT(std);
  STD_PREV(STD_NEXT(std)) = stdprev;
  wrilts(fg);

  rdilts(fgPre);
  stdprev = STD_LAST;
  STD_NEXT(stdprev) = std;
  STD_PREV(std) = stdprev;
  STD_NEXT(std) = 0;
  STD_LAST = std;
  wrilts(fgPre);

  STD_MOVED(std) = TRUE;
  astStmt = STD_AST(std);
  astCond = mk_count_test(A_CONDG(astStmt), pastCounts[FG_LOOP(fg)]);
  A_CONDP(astStmt, astCond);
  /*
   * This is where fgPost needs be associated with astCond & saved
   * in a data strucutre for mk_ztrips(int lp).  Problably need to
   * repurpose A_COND.
fprintf(STDERR,
"XXX lp:%d, fgPre:%d, fgPost:%d, fgPost_stdfirst:%d\n",
FG_LOOP(fg), fgPre, fgPost, FG_STDFIRST(fgPost));
   */
}

/* If necessary create new preheader and trailer nodes for loop #lp,
 * and set fgPre & fgPost to these nodes. */
static void
mk_loop_hdtl(int lp)
{
  int ast;
  int std;

  if (fgPre)
    return;

  fgPre = add_fg(FG_LPREV(LP_HEAD(lp)));
  opt.pre_fg = fgPre;
  FG_FT(fgPre) = TRUE;
  std = FG_STDFIRST(fgPre);
  if (!std) {
    /* Node empty; add a CONTINUE statement. */
    rdilts(fgPre);
    ast = mk_stmt(A_CONTINUE, 0);
    std = add_stmt_after(ast, 0);
    wrilts(fgPre);
  }
  FG_LINENO(fgPre) = STD_LINENO(std) = FG_LINENO(LP_HEAD(lp));
  add_loop_preheader(lp);

  add_single_loop_exit(lp);
  fgPost = FG_LNEXT(LP_TAIL(lp));
}

/** \brief If necessary create new preheader and trailer nodes for loop \#lp,
    and set fgPre & fgPost to these nodes. */
void
add_loop_hd(int lp)
{
  int ast;
  int std;

  fgPre = add_fg(FG_LPREV(LP_HEAD(lp)));
  opt.pre_fg = fgPre;
  FG_FT(fgPre) = TRUE;
  std = FG_STDFIRST(fgPre);
  if (!std) {
    /* Node empty; add a CONTINUE statement. */
    rdilts(fgPre);
    ast = mk_stmt(A_CONTINUE, 0);
    std = add_stmt_after(ast, 0);
    wrilts(fgPre);
  }

  add_loop_preheader(lp);
}

/* Augment the current conjunction of count tests, astCond, with a new test
 * that the loop count, astCount, is greater than 0. */
static int
mk_count_test(int astCond, int astCount)
{
  int ast, astNewCond;

  assert(astCount, "mk_count_test: loop not countable", 0, 4);
  if (A_TYPEG(astCount) == A_CNST)
    return astCond;
  ast = mk_binop(OP_GT, astCount, astb.i0, DT_LOG);
  if (astCond)
    astNewCond = mk_binop(OP_LAND, astCond, ast, DT_LOG);
  else
    astNewCond = ast;
  return astNewCond;
}

/* Create a CONTINUE statement, with COND field astCond,
 * in node fgPost, and return the STD. */
static int
mk_dealloc_stub(int astCond)
{
  int ast = mk_stmt(A_CONTINUE, 0);
  int std;

  A_CONDP(ast, astCond);
  rdilts(fgPost);
  std = add_stmt_after(ast, 0);
  wrilts(fgPost);
  STD_LINENO(std) = FG_LINENO(fgPost);
  return std;
}

/*
 * Create zero-trip tests around hoisted communication statements within
 * loop lp.
 */
static void
mk_ztrips(int lp)
{
  int lpi;
  int fg;
  int std, stdend;
  int ast, astCond, astNewCond;
  int *pastConds = NULL;
  int maxconds = opt.nloops + 1;
  int nconds;

  for (lpi = LP_CHILD(lp); lpi; lpi = LP_SIBLING(lpi))
    mk_ztrips(lpi);

  NEW(pastConds, int, maxconds);
  for (fg = LP_FG(lp); fg; fg = FG_NEXT(fg)) {
    nconds = 0;
    std = FG_STDFIRST(fg);
    if (!std)
      continue;
    stdend = STD_NEXT(FG_STDLAST(fg));
    for (; std != stdend; std = STD_NEXT(std)) {
      astCond = A_CONDG(STD_AST(std));
      astNewCond = astCond;
      for (; nconds; nconds--) {
        if (astCond)
          astNewCond = remove_cond(astCond, pastConds[nconds - 1]);
        else
          astNewCond = astCond;
        if (astNewCond != astCond)
          /* ...new condition contained in old. */
          break;
        /* ...new condition not in old, so terminate old condition. */
        ast = mk_stmt(A_ENDIF, 0);
        add_stmt_before(ast, std);
      }
      if (!astCond || !astNewCond)
        continue;
      ast = mk_stmt(A_IFTHEN, 0);
      A_IFEXPRP(ast, astNewCond);
      add_stmt_before(ast, std);
      pastConds[nconds++] = astCond;
      assert(nconds <= opt.nloops, "mk_ztrips: too many conditions", lp, 4);
    }
    /*
     * f20730 - hoisting of invariant assignments is suboptimal for
     * reaching defs.  Hoisting an invariant assignment from a loop
     * occurs as follows:
     *
     * the loop
     * ---------
     *   do __the_loop__
     *     XX = __invariant_expression__
     *     __work__ << uses XX>
     *   enddo
     *
     * the hoist
     * ---------
     *   if (zero-trip-test) then
     *     XX = __invariant_expression__
     *   endif
     *   do __the_loop__
     *     __work__ <<< uses XX>>>
     *   enddo
     *
     * this needs to be
     * ----------------
     *   if (zero-trip-test) then
     *     XX = __invariant_expression__
     *     do __the_loop__  !!! and somehow elide the backend's ZT
     *       __work__ << uses XX>
     *     enddo
     *   endif
     *
     * THE PROBLEM
     * -----------
     * IF there exists a def of XX prior to the original loop, that def
     * now reaches the XX within the transformed loop (the hoisted
     * assignment is now guarded).  Without the hoisting, XX was killed
     * by the def of XX in the loop.  This can lead to false uses of
     * XX -- for WRF, this was especially bad given that the XX was
     * considered a live-out of a loop, which inhibited vectorizing
     * that loop.
     *
     * To correctly place the ENDIF, move_invar_std() needs to associate
     * A_COND with fgPost (FG_STDFIRST(fgPost) would be the correct
     * insert point for the ENDIF.
     *
     * AT THIS TIME, it is not be worth the effort -- the backend will
     * still hoist the RHS within the correct zero-trip context.
     */
    for (; nconds; nconds--) {
      ast = mk_stmt(A_ENDIF, 0);
      add_stmt_before(ast, std);
    }
  }
  FREE(pastConds);
}

/* Remove conjunction astSrc from conjunction astTarg. If astSrc not in
 * astTarg, return astTarg. If astSrc = astTarg, return 0. */
static int
remove_cond(int astTarg, int astSrc)
{
  int astNewl, astNewr, astNew;

  if (astSrc == astTarg)
    return 0;
  if (A_TYPEG(astTarg) != A_BINOP || A_OPTYPEG(astTarg) != OP_LAND)
    return astTarg;
  astNewl = remove_cond(A_LOPG(astTarg), astSrc);
  if (!astNewl) {
    astNewr = remove_cond(A_ROPG(astTarg), astSrc);
    return astNewr;
  }
  astNewr = remove_cond(A_ROPG(astTarg), astSrc);
  if (!astNewr) {
    astNewl = remove_cond(A_LOPG(astTarg), astSrc);
    return astNewl;
  }
  if (astNewl == A_LOPG(astTarg) && astNewr == A_ROPG(astTarg))
    astNew = astTarg;
  else
    astNew = mk_binop(OP_LAND, astNewl, astNewr, DT_LOG);
  return astNew;
}
