// $Id: OMPinfo.java,v 1.30 2001/03/21 07:56:39 msato Exp $
// $RWC_Release: Omni-1.6 $
// $RWC_Copyright:
//  Omni Compiler Software Version 1.5-1.6
//  Copyright (C) 2002 PC Cluster Consortium
//  
//  This software is free software; you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License version
//  2.1 published by the Free Software Foundation.
//  
//  Omni Compiler Software Version 1.0-1.4
//  Copyright (C) 1999, 2000, 2001.
//   Tsukuba Research Center, Real World Computing Partnership, Japan.
//  
//  Please check the Copyright and License information in the files named
//  COPYRIGHT and LICENSE under the top  directory of the Omni Compiler
//  Software release kit.
//  
//  
//  $
package exc.openmp;

import java.io.*;
import exc.object.*;
import exc.block.*;

import java.util.Vector;

//
// information for each OMP directive
// 
public class OMPinfo {
  OMPinfo parent;
  Block block;		/* back link */
  int pragma;		/* directives */
  Xobject arg;
  OMPvar varlist;	/* variable list */
  Xobject id_list;

  // for OMP.FLUSH
  Vector flush_vars;

  // for OMP.FOR
  int sched;		/* for DIR_SCHEDULE */
  Xobject sched_chunk;
  boolean ordered;

  // for parallel region, OMP.PARALLEL
  int data_default = OMP.DATA_DEFAULT_SHARED;
  Xobject if_expr;
  boolean no_wait;
  Vector region_args;	// for parallel region
  Vector region_params;

  // for fuction body, OMP.FUNCTION_BODY, OMP.PARALLEL
  OMPfileEnv env;
  Vector thdprv_id_list;
  Vector thdprv_local_id_list;

  public OMPinfo(int pragma,OMPinfo parent,Block b,
		 OMPfileEnv env){
    this.pragma = pragma;
    this.parent = parent;
    this.block = b;
    this.env = env;
    id_list = Xcons.emptyIdList();

    if(pragma == OMP.PARALLEL) {
      region_args = new Vector();
      region_params = new Vector();

      /* check local tagnames, replicate them. */
      for(Block bb = b.getParentBlock(); bb != null; bb = bb.getParentBlock()){
	BlockList b_list = bb.getBody();
	if(b_list == null) continue;
	Xobject ids = b_list.getIdentList();
	if(ids == null) continue;
	for(XobjArgs a = ids.getArgs(); a != null; a = a.nextArgs()){
	  Ident id = (Ident)a.getArg();
	  /* check already declared or not. */
	  if(id.getStorageClass() == StorageClass.TAGNAME){
	    for(XobjArgs aa = id_list.getArgs(); aa != null; 
		aa = aa.nextArgs()){
	      if(((Ident)aa.getArg()).getName().equals(id.getName())){
		id = null;
		break;
	      }
	    }
	    if(id != null) id_list.add(id.copy());
	  }
	}
      }
    }

    if(pragma == OMP.PARALLEL || pragma == OMP.FUNCTION_BODY){
      thdprv_id_list = new Vector();
      thdprv_local_id_list = new Vector();
    }
  }
    
  OMPvar findOMPvar(String name){
    for(OMPvar v = varlist; v != null; v = v.next)
      if(v.id.getName().equals(name)) return v;
    return null;
  }

  OMPvar findOMPvar(Ident id){
    for(OMPvar v = varlist; v != null; v = v.next)
      if(v.id == id) return v;
    return null;
  }

  // for checking implicit private in FOR directive
  boolean isPrivateOMPvar(String name){
    OMPvar v = null;
    Ident id = null;
    for(Block b = block ; b != null; b = b.getParentBlock()){
      BlockList b_list = b.getBody();
      if(b_list == null) continue;
      // check local declaration
      if((id = b_list.findLocalIdent(name)) != null) break;
      OMPinfo i = (OMPinfo) b.getProp(OMP.prop);
      if(i == null) continue;
      // if declared as OMPvar, check it
      if((v = i.findOMPvar(name)) != null) break;
      if(i.pragma == OMP.PARALLEL) break; // not beyond parallel region
    }
    if(id != null){
      int stg = id.getStorageClass();
      if(stg == StorageClass.PARAM || 
	 stg == StorageClass.FPARAM || 
	 stg == StorageClass.AUTO ||
	 stg == StorageClass.REGISTER ||
	 stg == StorageClass.TEMP ||
	 stg == StorageClass.CTEMP)
	return true;
    }
    if(v != null && v.is_private) return true;
    else return false;
  }

  //
  // build OMP variable symbol table
  //
  OMPvar declOMPvar(String name, int atr){
    OMPvar v = null;
    Ident id = null;

    if(OMP.debugFlag)
      System.out.println("declOMPvar name="+name+", atr="+atr);

    // check special case: firstprivate and lastprivate
    if((v = this.findOMPvar(name)) != null){
      if((v.atr == OMP.DATA_FIRSTPRIVATE &&
	  atr == OMP.DATA_LASTPRIVATE) ||
	 (atr == OMP.DATA_FIRSTPRIVATE &&
	  v.atr == OMP.DATA_LASTPRIVATE)){
	v.is_first_private = true;
	v.is_last_private = true;
	return v;
      }
    }

    // find local data scope attribute
    for(Block b = block ; b != null; b = b.getParentBlock()){
      BlockList b_list = b.getBody();
      if(b_list == null) continue;
      // check local declaration
      if((id = b_list.findLocalIdent(name)) != null) break;
      OMPinfo i = (OMPinfo) b.getProp(OMP.prop);
      if(i == null) continue;
      // if declared as OMPvar, check it
      if((v = i.findOMPvar(name)) != null) break;
      if(i.pragma == OMP.PARALLEL) break; // not beyond parallel region
    }
    if(id != null){
      int stg = id.getStorageClass();
      if(stg == StorageClass.PARAM || 
	 stg == StorageClass.FPARAM || 
	 stg == StorageClass.AUTO ||
	 stg == StorageClass.REGISTER){
	if(atr != OMP.DATA_PRIVATE)
	  OMP.warning(block.getLineNo(),
		      "local variable '"+name+"' is already private");
	return null;
      }
    }
    if(v != null){
      if(v.is_private){
	OMP.warning(block.getLineNo(),
		    "variable '"+name+"' is already defined as private");
	return v;
      }
      // already declared as shared
      if(v.is_shared && atr == OMP.DATA_SHARED){
	return v;	// nested shared ????
      }
      //      else {
      //	OMP.error(block.getLineNo(),
      //		  "variable '"+name+"' is already defined");
      //	return null;
      //      }
    } 

    id = block.findIdent(name);
    if(id == null){
      OMP.error(block.getLineNo(),
		"undefined variable in clause, '"+name+"'");
      return null;
    }
    if(id.Type().isFunction()){
      OMP.error(block.getLineNo(),
		"data '"+name+"' in cluase is a function");
      return null;
    }
    if(OMP.DATA_reduction(atr)){
      if(!id.Type().isScalar() && !Xtype.isFComplex(id.Type())){
	OMP.error(block.getLineNo(),
		  "non-scalar variable '"+
		  name+"' in reduction clause");
	return null;
      }
    }

    if(env.isThreadPrivate(id)){
      if(atr != OMP.DATA_COPYIN){
	OMP.error(block.getLineNo(),
		  "variable '"+name+"' is threadprivate");
	return null;
      }
      // reference threadprivate variable
      OMPvar thdprv_var = refOMPvar(block,id.getName());
      v = new OMPvar(id,OMP.DATA_COPYIN);
      v.shared_addr = thdprv_var.shared_addr;
      v.next = varlist;
      varlist = v;
      return v;
    } else { 
      // not threadprivate
      if(atr == OMP.DATA_COPYIN){
	if(id.getStorageClass() == StorageClass.FCOMM){
	  OMP.error(block.getLineNo(), 
		    "threadprivate named variable "+name+
		    " in COPYIN clause inis not supported yet");
	  return null;
	}
	OMP.error(block.getLineNo(),
		  "non-threadprivate'"+name+"' in COPYIN clause");
	return null;
      }
    }

    v = new OMPvar(id,atr);

    Xtype t = id.Type();
    if(t.isArray() && t.getArrayDim() == -1 &&
       v.adj_array_size == null){  /* adjustable array */
      v.adj_array_size = 
	Xcons.binaryOp(Xcode.MUL_EXPR,
		       refOMPvarExpr(block,t.getArrayAdjSize().copy()),
		       Xcons.IntConstant(t.getRef().getSize()));
    }

    if(v.is_private){
      if(v.adj_array_size != null){
	/* for adjustable array, convert local copy into pointer */
	Ident local_id = Ident.Local("_p_"+id.getName(),
				     Xtype.Pointer(t.getRef()));
	id_list.add(local_id);
	v.private_addr = local_id.Ref();
      } else if(t.getSize() == 0){
	OMP.error(block.getLineNo(),
		  "private variable for '"+id.getName()+"', size is unknown");
      } else {
	// allocate new variable in this context
	Ident local_id = Ident.Local("_p_"+id.getName(),id.Type());
	id_list.add(local_id);
	v.private_addr = local_id.getAddr();
      }
    } 

    if(v.is_shared){
      // need to be shared. declared in parallel region
      v.shared_addr = sharedAddr(block,id);
    }

    v.next = varlist;
    varlist = v;
    return v;
  }

  Xobject sharedAddr(Block block, Ident id){
    Block b;
    BlockList b_list;
    OMPinfo i;
    OMPvar v;

    // check private declaration for global.
    int stg = id.getStorageClass();
    if(stg == StorageClass.EXTERN ||
       stg == StorageClass.EXTDEF ||
       stg == StorageClass.FCOMM ||
       stg == StorageClass.STATIC){
      // search local declaration for global
      for(b = block; b != null; b = b.getParentBlock()){
	// if declared in local context.
	if(stg != StorageClass.FCOMM &&
	   (b_list = b.getBody()) != null &&
	    b_list.findLocalIdent(id) != null) break;

	// if declared as OMPvar, return it address (fall through)
	if((i = (OMPinfo) b.getProp(OMP.prop)) != null &&
	   (v = i.findOMPvar(id)) != null && v.is_private) break;
      }
      if(b == null) return id.getAddr();
    } 

    // search parallel section to be declared.
    for(b = block ; b != null; b = b.getParentBlock()){
      // if declared as local variable, reference it.
      if((b_list = b.getBody()) != null && 
	 b_list.findLocalIdent(id) != null) 
	return id.getAddr();

      if((i = (OMPinfo) b.getProp(OMP.prop)) == null) continue;

      // if declared as OMPvar, return it address
      if((v = i.findOMPvar(id.getName())) != null)
	return v.getAddr();

      if(i.pragma == OMP.PARALLEL){
	// make local variable to pass across parallel section.
	Ident id_local = 
	  Ident.Local("_pp_"+id.getName(),Xtype.Pointer(id.Type()));

	// check already defined or not.
	for(int k = 0; k < i.region_params.size(); k++)
	  if(((Ident)(i.region_params.elementAt(k))).
	     getName().equals(id_local.getName()))
	    return id_local.Ref();
	    
	i.id_list.add(id_local);
	// get reference outside block.
	Xobject addr;
	if((v = refOMPvar(b.getParentBlock(),id.getName())) != null)
	  addr = v.getAddr();
	else addr = id.getAddr();
	i.region_args.addElement(addr);
	i.region_params.addElement(id_local);
	return id_local.Ref();
      }
    }
    OMP.fatal("sharedAddr: id is not found, id ="+id);
    return null;
  }

  OMPvar declThreadPrivate(Ident id){
    OMPvar v = findOMPvar(id);
    if(v != null) return v;
    v = new OMPvar(id,OMP.DATA_NONE);
    if(OMP.leaveThreadPrivateFlag) {
      v.shared_addr = id.getAddr();
    } else {
      Ident id_local = 
	Ident.Local("_ppthd_"+id.getName(),Xtype.Pointer(id.Type()));
      thdprv_local_id_list.addElement(id_local);
      thdprv_id_list.addElement(id);
      v.shared_addr = id_local.Ref();
    }
    v.next = varlist;
    varlist = v;
    return v;
  }

  // reference OMPvar in 'block'
  static OMPvar refOMPvar(Block b, String name){
    OMPinfo i;
    OMPvar v;
    Ident id;
    for( ; b != null; b = b.getParentBlock()){
      BlockList b_list = b.getBody();
      if(b_list != null && b_list.findLocalIdent(name) != null) 
	return null;  // defined local variable inside.
      if((i = (OMPinfo) b.getProp(OMP.prop)) != null){
	if((v = i.findOMPvar(name)) != null) return v;
	if(i.pragma == OMP.PARALLEL){
	  /* not found, then declare variable as default attribute */
	  if((id = i.env.findThreadPrivate(b,name)) != null)
	    return i.declThreadPrivate(id);

	  id = b.findIdent(name);

	  if(id == null) return null;	// genrated variable???

	  if(id.getStorageClass() == StorageClass.TEMP){
	    /* front-end compiler temporary, replicate it. */
	    return i.declOMPvar(name,OMP.DATA_PRIVATE);
	  }
	  if(id.getStorageClass() == StorageClass.CTEMP){
	    /* front-end compiler read-only temporary, make it FIRSTPRIVATE */
	    return i.declOMPvar(name,OMP.DATA_FIRSTPRIVATE);
	  }

	  if(i.data_default == OMP.DATA_DEFAULT_SHARED)
	    return i.declOMPvar(name,OMP.DATA_SHARED);
	  else if(i.data_default == OMP.DATA_DEFAULT_PRIVATE)
	    return i.declOMPvar(name,OMP.DATA_PRIVATE);
	  else {
	    OMP.error(b.getLineNo(),
		      "unknown data attribute on '"+
		      name+"', default data attribute is 'none'");
			
	  }
	} else if(i.pragma == OMP.FUNCTION_BODY){
	  if((id = i.env.findThreadPrivate(b,name)) != null)
	    return i.declThreadPrivate(id);
	}
      }
    }
    /* reference from orphan-directives */
    return null;
  }

  static Xobject refOMPvarExpr(Block b,Xobject x){
    OMPvar v;
    if(x == null) return null;
    XobjectIterator i = new bottomupXobjectIterator(x);
    for(i.init(); !i.end(); i.next()){
      Xobject xx = i.getXobject();
      if(xx == null) continue;
      if(xx.isVariable()){
	if((v = refOMPvar(b,xx.getName())) == null) continue;
	i.setXobject(v.Ref());
      } else if(xx.isVarAddr()){
	if((v = refOMPvar(b,xx.getName())) == null) continue;
	i.setXobject(v.getAddr());
      } else if(xx.isArrayAddr()){
	if((v = refOMPvar(b,xx.getName())) == null) continue;
	if(v != null) i.setXobject(v.getAddr());
      } 
    }
    return i.topXobject();
  }
    
  void setSchedule(Xobject sched_arg){
    if(sched_arg == null) return;	// for error recovery

    if(sched != OMP.SCHED_NONE){
      OMP.error(block.getLineNo(),
		"schedule clause is already specified");
      return;
    }
    sched = sched_arg.getArg(0).getInt();
    sched_chunk = sched_arg.getArg(1);
    if(sched == OMP.SCHED_RUNTIME && sched_chunk != null)
      OMP.error(block.getLineNo(),
		"schedule RUNTIME has chunk argument");
    if(sched == OMP.SCHED_AFFINITY)
      OMP.error(block.getLineNo(),
		"schedule AFFINITY is not supported in this mode");
    sched_chunk = refOMPvarExpr(block.getParentBlock(),sched_chunk);
    if(OMP.debugFlag)
      System.out.println("schedule("+sched+","+sched_chunk+")");
  }

  void setIfExpr(Xobject cond){
    if_expr = refOMPvarExpr(block.getParentBlock(),cond);
    if(!if_expr.Type().isIntegral())
      if_expr = Xcons.Cast(Xtype.intType,if_expr);
    if(OMP.debugFlag)
      System.out.println("parallel if = "+if_expr);
  }

  void setFlushVars(Xobject list){ // list is list of IDENT
    if(list == null) return;
    flush_vars = new Vector();
    for(XobjArgs a = list.getArgs(); a != null; a = a.nextArgs()){
      Xobject x = a.getArg();
      OMPvar v = refOMPvar(block,x.getName());
      if(v != null){
	if(v.is_private){
	  OMP.error(block.getLineNo(),
		    "private variable in flush, '"+x.getName()+"'");
	  continue;
	}
	if(!v.is_shared) OMP.fatal("not shared variable in flush '"
				   +x.getName()+"'");
	flush_vars.addElement(Xcons.List(v.shared_addr,v.getSize()));
      } else { /* local or global from orphan */
	Ident id = block.findIdent(x.getName());
	if(id == null || 
	   (id.getStorageClass() != StorageClass.EXTERN &&
	    id.getStorageClass() != StorageClass.EXTDEF &&
	    id.getStorageClass() != StorageClass.STATIC &&
	    id.getStorageClass() != StorageClass.FCOMM &&
	    id.getStorageClass() != StorageClass.FPARAM)){
	  OMP.error(block.getLineNo(),
		    "local or undefined variable in flush, '"+x.getName()+"'");
	  continue;
	} else {
	  Xtype t = id.Type();
	  Xobject size;
	  if(t.isArray() && t.getArrayDim() == -1){
	    size = Xcons.binaryOp(Xcode.MUL_EXPR,
				   refOMPvarExpr(block,
						 t.getArrayAdjSize().copy()),
				   Xcons.IntConstant(t.getRef().getSize()));
	  } else {
	    size = Xcons.IntConstant(t.getSize());
	  }
	  flush_vars.addElement(Xcons.List(id.getAddr(),size));
	}
      }
    }
  }

  OMPinfo findContext(int pragma){
    for(OMPinfo i = this.parent; i != null; i = i.parent)
      if(i.pragma == pragma) return i;
    return null;
  }

  OMPinfo findContext(int pragma,Xobject arg){
    for(OMPinfo i = this.parent; i != null; i = i.parent)
      if(i.pragma == pragma && i.arg == arg) return i;
    return null;
  }

  public Vector getRegionArgs() {
    return (region_args);
  }

  public Vector getRegionParams() {
    return (region_params);
  }

  public Xobject getIfExpr() {
    return (if_expr);
  }

  public Xobject getIdList() {
    return (id_list);
  }

  public int getSched() {
    return (sched);
  }
  
  public void setSchedChunk(Xobject new_chunk) {
    sched_chunk = new_chunk;
  }
  
  public Xobject getSchedChunk() {
    return (sched_chunk);
  }

  public boolean isOrdered() {
    return (ordered);
  }
  
  public boolean isNoWait() {
    return (no_wait);
  }
}

