/*
 * Copyright (c) 2015-2018, 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 Functions for dealing with lexical scopes and the lifetimes
   of scoped variables.


   A lexical scope is represented by an ST_BLOCK symbol table entry. The
   scope's ENCLFUNC field points to either the enclosing scope or the
   containing function. Scopes have two pairs of start/end labels:

   STARTLAB / ENDLAB: These labels are only inserted when generating debug
   info. The are tagged as volatile so they won't be removed, and they can be
   used to generate complete DWARF lexical scope information.

   BEGINSCOPELAB / ENDSCOPELAB: These labels are inserted in-stream as ilts
   with an IL_LABEL instruction. The corresponding ST_LABEL symbol table
   entries are tagged with the corresponding BEGINSCOPE / ENDSCOPE flags, and
   the ENCLFUNC field points back to the ST_BLOCK entry.

   Every local variable has a scope indicated by its ENCLFUNC field. Variables
   with function scope point to the function's symbol table entry instead of an
   ST_BLOCK entry.
*/

#include "scope.h"
#include "error.h"
#include "global.h"
#include "ilm.h"
#include "ilmtp.h"
#include "ili.h"
#include "go.h"
#include "scope.h"
#include "flang/ADT/hash.h"
#include "symfun.h"

/* For addlabel(). */
#include "semant.h"

#define VALIDSYM(sptr) ((sptr) > NOSYM && (sptr) < stb.stg_avail)

/**
   \brief Is sptr a symbol table entry that can be the root of a scope
   tree?  For C, this is functions. For Fortran, this includes entries
   and procedures.
 */
static bool
is_scope_root(SPTR sptr)
{
  switch (STYPEG(sptr)) {
  case ST_PROC:
  case ST_ENTRY:
    return true;
  default:
    return false;
  }
}

/**
   \brief Return true if the scope 'outer' contains the scope 'inner'.

   Both outer and inner must be valid ST_BLOCK or ST_FUNC symbol table
   entries.
 */
bool
scope_contains(SPTR outer, SPTR inner)
{
  ICHECK(VALIDSYM(outer) &&
         (is_scope_root(outer) || STYPEG(outer) == ST_BLOCK));
  ICHECK(VALIDSYM(inner) &&
         (is_scope_root(inner) || STYPEG(inner) == ST_BLOCK));

  while (STYPEG(inner) == ST_BLOCK) {
    if (inner == outer)
      return true;
    inner = ENCLFUNCG(inner);
  }

  return inner == outer;
}

/**
   \brief Return true if label_sptr is a scope label.
 */
bool
is_scope_label(int label_sptr)
{
  if (!VALIDSYM(label_sptr) || STYPEG(label_sptr) != ST_LABEL)
    return false;

  return BEGINSCOPEG(label_sptr) || ENDSCOPEG(label_sptr);
}

/**
   \brief Check if ilix is an IL_LABEL instruction referring to a
   scope label.
 */
bool
is_scope_label_ili(int ilix)
{
  return ILI_OPC(ilix) == IL_LABEL && is_scope_label(ILI_OPND(ilix, 1));
}

/**
   \brief Check if the bihx basic block contains nothing but scope
   labels (or is empty).
 */
bool
is_scope_labels_only_bih(int bihx)
{
  int iltx;

  for (iltx = BIH_ILTFIRST(bihx); iltx; iltx = ILT_NEXT(iltx)) {
    if (!is_scope_label_ili(ILT_ILIP(iltx)))
      return false;
  }
  return true;
}

/**
   \brief Verify the integrity of an ST_BLOCK symbol table entry and
   its associated labels.
 */
static void
verify_block(int block_sptr)
{
  int lab;

  ICHECK(VALIDSYM(block_sptr));
  ICHECK(STYPEG(block_sptr) == ST_BLOCK);

  /* STARTLAB and ENDLAB are not required to be present. */
  if (STARTLABG(block_sptr)) {
    lab = STARTLABG(block_sptr);
    ICHECK(VALIDSYM(lab));
    ICHECK(STYPEG(lab) == ST_LABEL);
    ICHECK(ENCLFUNCG(lab) == block_sptr);

    /* These flags should go on the BEGINSCOPELAB / ENDSCOPELAB
       labels, not here. */
    ICHECK(!BEGINSCOPEG(lab));
    ICHECK(!ENDSCOPEG(lab));
    /* FIXME: Check ILIBLK. */
  }

  if (ENDLABG(block_sptr)) {
    lab = ENDLABG(block_sptr);
    ICHECK(VALIDSYM(lab));
    ICHECK(STYPEG(lab) == ST_LABEL);
    ICHECK(ENCLFUNCG(lab) == block_sptr);
    ICHECK(!BEGINSCOPEG(lab));
    ICHECK(!ENDSCOPEG(lab));
    /* FIXME: Check ILIBLK. */
  }

  /* All blocks should have delineating labels. */
  lab = BEGINSCOPELABG(block_sptr);
  ICHECK(VALIDSYM(lab));
  ICHECK(STYPEG(lab) == ST_LABEL);
  ICHECK(ENCLFUNCG(lab) == block_sptr);
  ICHECK(BEGINSCOPEG(lab));
  ICHECK(!ENDSCOPEG(lab));
  /* FIXME: Check ILIBLK. */

  lab = ENDSCOPELABG(block_sptr);
  ICHECK(VALIDSYM(lab));
  ICHECK(STYPEG(lab) == ST_LABEL);
  ICHECK(ENCLFUNCG(lab) == block_sptr);
  ICHECK(!BEGINSCOPEG(lab));
  ICHECK(ENDSCOPEG(lab));
  /* FIXME: Check ILIBLK. */
}

static bool
is_local_variable(int sptr)
{
  switch (STYPEG(sptr)) {
  case ST_PLIST:
  case ST_VAR:
  case ST_ARRAY:
  case ST_STRUCT:
  case ST_UNION:
    switch (SCG(sptr)) {
    case SC_LOCAL:
      /* Somebody who understands Fortran should probably revisit this
         and check DINITG, SAVEG etc. */
      return true;
    /* Ignore SC_DUMMY. It's going to have function scope anyway. */
    default:
      return false;
    }
  default:
    return false;
  }
}

/**
   \brief Collect the set of locals referenced by the ILI tree starting at ilix.

   Loads and stores to local variables are always going to use an ACON
   to compute the address, so just find all the symbols referenced by
   ACONs.
 */
static void
collect_referenced_locals(hashset_t symbs, int ilix)
{
  ILI_OP opc = ILI_OPC(ilix);

  if (opc == IL_ACON) {
    /* ACON opnd 1 is an ST_CONST sptr with DT_CPTR type. */
    int sptr = CONVAL1G(ILI_OPND(ilix, 1));
    if (sptr && is_local_variable(sptr))
      hashset_replace(symbs, INT2HKEY(sptr));
  } else {
    int i;
    for (i = IL_OPRS(opc); i > 0; i--)
      if (IL_ISLINK(opc, i))
        collect_referenced_locals(symbs, ILI_OPND(ilix, i));
  }
}

/* Info about a single open scope. */
struct scope_info {
  /* ST_BLOCK for scope, or ST_FUNC/ST_PROC/ST_ENTRY for the function-level
     scope. */
  SPTR block;
  /* The IL_LABEL ilt that closes this scope. */
  int closing_ilt;
};

/* A stack of open scopes. */
struct scope_stack {
  /* scope[0..curr] are in use. */
  int curr;
  /* scope[0..outer] are perfectly nested. */
  int outer;
  /* Allocated entries in scope. */
  int allocated;
  struct scope_info *scope;
  /* scope[outer].block from before closed scopes were popped. */
  SPTR prev_top;
};

/**
   \brief Initialize a scope stack with func as the outermost scope.
 */
static void
init_scope_stack(struct scope_stack *ss, SPTR func)
{
  ss->curr = ss->outer = 0;
  ss->allocated = 100;
  NEW(ss->scope, struct scope_info, ss->allocated);
  memset(&ss->scope[0], 0, sizeof(ss->scope[0]));
  ss->scope[0].block = func;
  ss->prev_top = SPTR_NULL;
}

static void
push_block(struct scope_stack *ss, SPTR block)
{
  ++ss->curr;
  NEED(ss->curr + 1, ss->scope, struct scope_info, ss->allocated,
       ss->allocated + 100);
  memset(&ss->scope[ss->curr], 0, sizeof(ss->scope[0]));
  ss->scope[ss->curr].block = block;
  dbgprintf("  Pushing scope %d (%s) at level %d: enclfunc=%d (%s)\n", block,
            SYMNAME(block), ss->curr, ENCLFUNCG(block),
            SYMNAME(ENCLFUNCG(block)));
}

static void
pop_block(struct scope_stack *ss)
{
  dbgprintf("  Popping scope %d (%s) at level %d\n", ss->scope[ss->curr].block,
            SYMNAME(ss->scope[ss->curr].block), ss->curr);
  ICHECK(ss->curr > 0);
  if (--ss->curr < ss->outer)
    ss->outer = ss->curr;
}

/**
   \brief Check labels opening scopes in bih, and collect all symbols
   referenced in this block.

   Add opened scopes to ss.
 */
static void
verify_beginscopes(int bih, hashset_t locals, struct scope_stack *ss)
{
  int ilt;

  /* All of the scopes pushed in this block must be sub-scopes of the
     current top. */
  SPTR outer_scope = ss->scope[ss->curr].block;

  for (ilt = BIH_ILTFIRST(bih); ilt; ilt = ILT_NEXT(ilt)) {
    int ilix = ILT_ILIP(ilt);
    SPTR lab, blk;

    if (ILI_OPC(ilix) != IL_LABEL) {
      collect_referenced_locals(locals, ilix);
      continue;
    }

    /* This is an IL_LABEL ilt. */
    lab = ILI_SymOPND(ilix, 1);
    ICHECK(VALIDSYM(lab) && STYPEG(lab) == ST_LABEL);
    if (!BEGINSCOPEG(lab))
      continue;

    /* This is a scope-opening label. */
    blk = ENCLFUNCG(lab);
    verify_block(blk);
    ICHECK(BEGINSCOPELABG(blk) == lab);

    /* If multiple scopes are pushed in the same BIH, we'll allow any
       order. They all have to be proper sub-scopes of outer_scope,
       though. */
    push_block(ss, blk);
    ICHECK((blk != outer_scope) && "Can't open the same scope twice");
    ICHECK(scope_contains(outer_scope, blk) &&
           "New scope is not properly nested");
  }
}

/**
   \brief Verify a local that was referenced in the current function.

   This function conforms to the prototype expected by hashset_iterate.
 */
static void
verify_local(hash_key_t key, void *context)
{
  int sptr = HKEY2INT(key);
  struct scope_stack *ss = (struct scope_stack *)context;
  SPTR encl = ENCLFUNCG(sptr);
  int i;

/* The Fortran frontend doesn't bother setting ENCLFUNC on local
   variables.  Fortran doesn't have scopes anyway, except through
   inlining. */
  if (!encl)
    return;

  /* Compiler-created scalar temporaries don't always have associated
     scopes.  They will be treated as function-scope. */
  if (!encl && CCSYMG(sptr)) {
    dbgprintf("  Referenced local compiler-created temporary with no "
              "scope: %d (%s)\n",
              sptr, SYMNAME(sptr));
    return;
  }

  dbgprintf("  Referenced local %d (%s) from scope %d. At block %d level "
            "%d-%d, prev %d.\n",
            sptr, SYMNAME(sptr), encl, ss->scope[ss->curr].block, ss->outer,
            ss->curr, ss->prev_top);
  ICHECK(VALIDSYM(encl) && "Local variable does not have a valid scope");

  /* Now verify that the 'encl' scope is accessible in one of the
     active scopes in the current block.

     It is possible that multiple non-overlapping scopes were pushed
     and popped in the current basic block. When that happens, these
     scopes will appear on the scope stack from ss->outer to
     ss->curr. These scopes are pushed even though they are not
     nested. They will be popped again by verify_endscopes().

     We are satisfied if encl contains any scope from this set. */
  for (i = ss->outer; i <= ss->curr; i++) {
    if (scope_contains(encl, ss->scope[i].block))
      return;
  }

  /* Finally, it is also possible that encl overlaps one of the scopes
     that ended in the current block. ss->prev_top records the
     innermost active scope on entry to the block.  The assertion here
     covers the above scope containment checks too. */
  ICHECK(scope_contains(encl, ss->prev_top) &&
         "Local variable referenced by code outside its scope");
}

/**
   \brief Process all the scope closing labels in bih.

   Pop the closed scopes off the stack.
 */
static void
verify_endscopes(int bih, struct scope_stack *ss)
{
  int ilt, i;
  int ilix;
  int lab, blk;
  bool closed_last_opened;

  for (ilt = BIH_ILTFIRST(bih); ilt; ilt = ILT_NEXT(ilt)) {
    ilix = ILT_ILIP(ilt);
    if (ILI_OPC(ilix) != IL_LABEL)
      continue;
    lab = ILI_OPND(ilix, 1);
    if (!ENDSCOPEG(lab))
      continue;

    /* This is a scope-closing label. */
    blk = ENCLFUNCG(lab);
    verify_block(blk);
    ICHECK(ENDSCOPELABG(blk) == lab);

    /* Find the scope that is being closed. It doesn't have to be the
       topmost one. */
    for (i = ss->curr; i > 0; i--) {
      if (ss->scope[i].block == blk) {
        /* We can't have duplicate labels closing the same scope. */
        ICHECK(ss->scope[i].closing_ilt == 0);
        ss->scope[i].closing_ilt = ilt;
        break;
      }
    }
  }

  /* Pop all the closed scopes. */
  while (ss->scope[ss->curr].closing_ilt != 0)
    pop_block(ss);
}

/**
   \brief Verify all of the locals referenced in the current function
   and check the scope labels.
 */
static void
verify_all_blocks(void)
{
  hashset_t locals = hashset_alloc(hash_functions_direct);
  struct scope_stack ss = {-1, 0, 0, 0};
  int bih;

  dbgprintf("\nVerifying scopes\n");
  init_scope_stack(&ss, GBL_CURRFUNC);

  /* Visit basic blocks in program order. */
  for (bih = gbl.entbih; bih; bih = BIH_NEXT(bih)) {
    int i, j;

    ss.prev_top = ss.scope[ss.curr].block;
    dbgprintf("block:%d in scope %d (%s)\n", bih, ss.prev_top,
              SYMNAME(ss.prev_top));

    /* First get rid of any open scopes that are closed in this block. */
    verify_endscopes(bih, &ss);

    /* Check labels opening scopes, collect all symbols referenced. */
    hashset_clear(locals);
    verify_beginscopes(bih, locals, &ss);

    /* The scope stack is now as deep as it gets in bih.  Check that
       all referenced locals belong to a pushed scope block. */
    hashset_iterate(locals, verify_local, &ss);

    /* Check labels closing scopes again. This takes care of scopes
       that were opened and closed in the same block. */
    verify_endscopes(bih, &ss);

    /* Check that new open scope are perfectly nested with respect to
       other open scopes. */
    for (i = ss.curr; i > ss.outer; i--) {
      if (ss.scope[i].closing_ilt == 0) {
        for (j = i - 1; j > ss.outer; j--)
          if (ss.scope[j].closing_ilt == 0)
            break;
        ICHECK(ENCLFUNCG(ss.scope[i].block) == ss.scope[j].block);
      }
    }
    ss.outer = ss.curr;
  }

  FREE(ss.scope);
  hashset_free(locals);
}

/**
   \brief Verify the integrity of data structures relating to variable
   scopes in the current function. Terminate compilation with a fatal
   error if anything is inconsistent.
 */
void
scope_verify(void)
{
#if DEBUG
  if (XBIT_USE_SCOPE_LABELS) {
    verify_all_blocks();
  }
#endif
}

/**
   \brief Insert a begin-scope label ILM for the scope blksym which
   much be an ST_BLOCK symbol table entry.

   \return the new ST_LABEL symbol table entry.
 */
int
insert_begin_scope_label(int block_sptr)
{
  int lab;

  ICHECK(VALIDSYM(block_sptr) && STYPEG(block_sptr) == ST_BLOCK);
  ICHECK(BEGINSCOPELABG(block_sptr) == 0 && "Scope already has a begin label.");

  if (!XBIT_USE_SCOPE_LABELS)
    return 0;

  lab = getlab();
  BEGINSCOPEP(lab, 1);
  ENCLFUNCP(lab, block_sptr);
  BEGINSCOPELABP(block_sptr, lab);
  addlabel(lab);

  return lab;
}

/**
   \brief Insert an end-scope label ILM for the scope blksym which
   much be an ST_BLOCK symbol table entry.

   \return the new ST_LABEL symbol table entry.
 */
int
insert_end_scope_label(int block_sptr)
{
  int lab;

  ICHECK(VALIDSYM(block_sptr) && STYPEG(block_sptr) == ST_BLOCK);
  ICHECK(ENDSCOPELABG(block_sptr) == 0 && "Scope already has an end label.");

  if (!XBIT_USE_SCOPE_LABELS)
    return 0;

  lab = getlab();
  ENDSCOPEP(lab, 1);
  ENCLFUNCP(lab, block_sptr);
  ENDSCOPELABP(block_sptr, lab);
  addlabel(lab);

  return lab;
}

/*
 * Scope tracking.
 *
 * During inlining, as we're scanning the current function ILMs top to bottom,
 * call track_scope_label(label_sptr) whenever an IM_LABEL is seen in order to
 * keep track of the current scope.
 *
 * The global variable current_scope points to the currently open scope.
 */
int current_scope = 0;

/**
   \brief Reset scope tracking, start from the outer function scope.
 */
void
track_scope_reset()
{
  current_scope = GBL_CURRFUNC;
  dbgprintf("Scope: Reset to current function %d:%s\n", current_scope,
            SYMNAME(current_scope));
}

/**
   \brief Update curent_scope after scanning over an IM_LABEL ilmx.
   The label sptr is the first and only ILM operand on IM_LABEL.
 */
void
track_scope_label(int label)
{
  assert(STYPEG(label) == ST_LABEL, "track_scope_label: Bogus label", label,
         ERR_Severe);

  /* If scope labels are not being inserted, it is impossible to keep
     track of the current scope. */
  if (!XBIT_USE_SCOPE_LABELS)
    return;

  /* Non-scope labels are simply ignored. */
  if (!BEGINSCOPEG(label) && !ENDSCOPEG(label))
    return;

  /* Labels left over after cancel_lexical_block() are also ignored. */
  if (!ENCLFUNCG(label))
    return;

  if (BEGINSCOPEG(label)) {
    dbgprintf("Scope: Enter scope %d:%s from %d:%s. Parent %d:%s.\n",
              ENCLFUNCG(label), SYMNAME(ENCLFUNCG(label)), current_scope,
              SYMNAME(current_scope), ENCLFUNCG(ENCLFUNCG(label)),
              SYMNAME(ENCLFUNCG(ENCLFUNCG(label))));
    current_scope = ENCLFUNCG(label);
    /* A label can't refer to a function scope. */
    assert(STYPEG(current_scope) == ST_BLOCK,
           "track_scope_label: Scope label does not refer to an ST_BLOCK",
           label, ERR_Severe);
  }

  if (ENDSCOPEG(label)) {
    dbgprintf("Scope: Leave scope %d:%s from %d:%s. Parent %d:%s.\n",
              ENCLFUNCG(label), SYMNAME(ENCLFUNCG(label)), current_scope,
              SYMNAME(current_scope), ENCLFUNCG(ENCLFUNCG(label)),
              SYMNAME(ENCLFUNCG(ENCLFUNCG(label))));
    /* This is the scope that's ending: */
    current_scope = ENCLFUNCG(label);
    assert(STYPEG(current_scope) == ST_BLOCK,
           "track_scope_label: Scope label does not refer to an ST_BLOCK",
           label, ERR_Severe);
    /* This is the active scope after leaving the current scope: */
    current_scope = ENCLFUNCG(current_scope);
  }

  if (!flg.inliner || XBIT(117, 0x10000)) {
    assert(current_scope == GBL_CURRFUNC || STYPEG(current_scope) == ST_BLOCK,
           "track_scope_label: Invalid scope label", label, ERR_Severe);
  }
}

/**
   \brief Find all the scope labels in the current ILM block and pass
   them to track_scope_label. This is used for blocks that don't
   contain any calls.

   This function is looking at numilms ILMs in the ilmb.ilm_base
   array. We don't have any ILM_* macros defined to access that area.

   Compare find_bpar() in inliner.c
 */
void
find_scope_labels(int numilms)
{
  int ilmx, len;

  if (!XBIT_USE_SCOPE_LABELS)
    return;

  for (ilmx = BOS_SIZE; ilmx < numilms; ilmx += len) {
    int opc = ilmb.ilm_base[ilmx];
    if (opc == IM_LABEL) {
      int label = ilmb.ilm_base[ilmx + 1];
      track_scope_label(label);
    }

    len = ilms[opc].oprs + 1; /* length for opcode and fixed operands */
    if (IM_VAR(opc))
      len += ilmb.ilm_base[ilmx + 1]; /* include the variable opnds */
  }
}

/*
 * Inliner support.
 *
 * The functions and global variables below are used by the C and Fortran
 * inliners.
 */

int new_callee_scope = 0;

/* Stack of simultaneously open callee scopes.
 *
 * This stack contains scopes whose begin label has been inserted, and whose
 * end label has yet to be inserted.
 *
 * The stack top is equal to new_callee_scope only after calling
 * begin_inlined_scope().
 */
static int *open_callee_scope = NULL;
static int open_callee_scope_count = 0;
static int open_callee_scope_capacity = 0;

/* Check if new_callee_scope is at the top of the open_callee_scope stack.
 *
 * This is false when new_callee_scope is 0 or when it hasn't been pushed yet
 * by begin_inlined_scope().
 */
#define NEW_CALLEE_SCOPE_IS_STACK_TOP \
  (open_callee_scope_count > 0 &&     \
   new_callee_scope == open_callee_scope[open_callee_scope_count - 1])

/**
   \brief The original function scope of the callee being inlined is
   represented as a new ST_BLOCK scope, stored in new_callee_scope.

   Call create_inlined_scope() to create new_callee_scope as a subscope of the
   currently tracked scope. Then call begin_inlined_scope() and
   end_inlined_scope() to insert the begin/end labels.
 */
void
create_inlined_scope(int callee_sptr)
{
  if (!XBIT_USE_SCOPE_LABELS) {
    /* Without scope labels, we don't know the scope containing the
       call site. Conservatively map scope to the entire function
       scope. */
    new_callee_scope = GBL_CURRFUNC;
    return;
  }

  if (!flg.inliner || XBIT(117, 0x10000)) {
    assert(current_scope, "create_inlined_scope: No current scope?", 0,
           ERR_Severe);
  }

  /* An existing callee scope should have been saved or cancelled. */
  if (new_callee_scope) {
    assert(NEW_CALLEE_SCOPE_IS_STACK_TOP,
           "create_inlined_scope: Previous callee scope wasn't saved",
           new_callee_scope, ERR_Fatal);
  }

  NEWSYM(new_callee_scope);
  STYPEP(new_callee_scope, ST_BLOCK);
  NMPTRP(new_callee_scope, NMPTRG(callee_sptr));
  SCOPEP(new_callee_scope, 1);
  ENCLFUNCP(new_callee_scope, current_scope);

  dbgprintf("Scope: Create %d:%s for inlining %d:%s.\n", new_callee_scope,
            SYMNAME(new_callee_scope), callee_sptr, SYMNAME(callee_sptr));
}

/**
   \brief Immediately before inlining a function, create a new
   ST_BLOCK to represent the callee's function scope in the current
   function.

   Insert an IM_LABEL ILM to open the new scope, update the
   new_callee_scope and current_scope variables to refer to the newly
   created scope.
 */
void
begin_inlined_scope(int func_sptr)
{
  int label;

  if (!XBIT_USE_SCOPE_LABELS)
    return;

  /* Make a new scope unless create_inlined_scope() has already been called.
   * Don't attempt to reuse an open scope that has already been pushed. */
  if (!new_callee_scope || NEW_CALLEE_SCOPE_IS_STACK_TOP)
    create_inlined_scope(func_sptr);

  assert(new_callee_scope && STYPEG(new_callee_scope) == ST_BLOCK,
         "begin_inlined_scope: No inlined scope was created", new_callee_scope,
         ERR_Severe);

  /* Push scope onto the stack of open callee scopes. */
  NEED(open_callee_scope_count + 1, open_callee_scope, int,
       open_callee_scope_capacity, open_callee_scope_capacity + 16);
  open_callee_scope[open_callee_scope_count++] = new_callee_scope;

  /* Create begin label here, end label in end_inlined_scope(). */
  label = insert_begin_scope_label(new_callee_scope);
  dbgprintf("Scope: Label %d:%s created, nest=%d.\n", label, SYMNAME(label),
            open_callee_scope_count);

  track_scope_label(label);
}

/**
   \brief After inlining a function, emit a scope-ending label.
 */
void
end_inlined_scope(void)
{
  int label;

  if (!XBIT_USE_SCOPE_LABELS)
    return;

  assert(new_callee_scope && STYPEG(new_callee_scope) == ST_BLOCK,
         "end_inlined_scope: Not currently in an inlined scope",
         new_callee_scope, ERR_Severe);

  label = insert_end_scope_label(new_callee_scope);
  dbgprintf("Scope: Label %d:%s created, next=%d.\n", label, SYMNAME(label),
            open_callee_scope_count);

  /* Pop this scope off the stack. Make new_callee_scope refer to the
   * previous open callee scope. */
  assert(NEW_CALLEE_SCOPE_IS_STACK_TOP,
         "end_inlined_scope: Scope stack corrupted", new_callee_scope,
         ERR_Fatal);
  if (--open_callee_scope_count)
    new_callee_scope = open_callee_scope[open_callee_scope_count - 1];
  else
    new_callee_scope = 0;

  track_scope_label(label);
}

/**
   \brief End currently open callee scopes until
   open_callee_scope_count is new_open_count.
 */
void
end_inlined_scopes(int new_open_count)
{
  while (open_callee_scope_count > new_open_count) {
    new_callee_scope = open_callee_scope[open_callee_scope_count - 1];
    end_inlined_scope();
  }
}

/**
   \brief If create_inlined_scope() was called, but the inlining was
   abandoned before any labels were inserted, call
   cancel_inlined_scope() to clean up.
 */
void
cancel_inlined_scope(void)
{
  if (!XBIT_USE_SCOPE_LABELS)
    return;

  if (!new_callee_scope)
    return;

  /* If the begin label was already inserted, this scope can't be cancelled. */
  if (NEW_CALLEE_SCOPE_IS_STACK_TOP)
    return;

  /* Go back to the scope we came from before calling
   * begin_inlined_scope(). */
  current_scope = ENCLFUNCG(new_callee_scope);
  dbgprintf("Scope: Reverted to %d:%s scope.\n", current_scope,
            SYMNAME(current_scope));

  /* Restore the old stack top into new_callee_scope. */
  if (open_callee_scope_count)
    new_callee_scope = open_callee_scope[open_callee_scope_count - 1];
  else
    new_callee_scope = 0;
}

void
reset_new_callee_scope()
{
  new_callee_scope = 0;
}

/**
   \brief Remove any scope labels from all blocks.  This is done as a
   workaround until all the phases of the compiler accept (or ignore)
   scope labels.
 */
void
remove_scope_labels(void)
{
  int bihx, iltx, nextiltx;
  for (bihx = gbl.entbih; 1; bihx = BIH_NEXT(bihx)) {
    rdilts(bihx);
    for (iltx = BIH_ILTFIRST(bihx); iltx; iltx = nextiltx) {
      nextiltx = ILT_NEXT(iltx);
      if (is_scope_label_ili(ILT_ILIP(iltx)))
        delilt(iltx);
    }
    wrilts(bihx);
    if (BIH_LAST(bihx))
      break;
  }
} /* remove_scope_labels */
