changes to get last benchmark working well
authorbdemsky <bdemsky>
Mon, 13 Jul 2009 00:35:22 +0000 (00:35 +0000)
committerbdemsky <bdemsky>
Mon, 13 Jul 2009 00:35:22 +0000 (00:35 +0000)
Robust/src/Analysis/Locality/DelayComputation.java
Robust/src/Analysis/Locality/DiscoverConflicts.java
Robust/src/IR/Flat/BuildCode.java
Robust/src/IR/State.java
Robust/src/IR/TypeDescriptor.java
Robust/src/buildscript

index b7c48bbf0c05a9e8decfeeef2e227964f275cea4..011938a17e29a3a69f484ad4a1fae1e7d9ee02df 100644 (file)
@@ -51,7 +51,7 @@ public class DelayComputation {
     }
 
     //ignore things that aren't in the map
-    dcopts=new DiscoverConflicts(locality, state, typeanalysis, cannotdelaymap, false, false);
+    dcopts=new DiscoverConflicts(locality, state, typeanalysis, cannotdelaymap, false, false, state.READSET?gft:null);
     dcopts.doAnalysis();
 
 
index 6dfc4a274cb96fce11893370aa0fbd5cbfc55233..11c4cb3525feb440e176d37b4487d00a698be9bf 100644 (file)
@@ -12,6 +12,7 @@ import IR.TypeDescriptor;
 import IR.MethodDescriptor;
 import IR.FieldDescriptor;
 import Analysis.Liveness;
+import Analysis.Loops.GlobalFieldType;
 
 public class DiscoverConflicts {
   Set<FieldDescriptor> fields;
@@ -20,6 +21,8 @@ public class DiscoverConflicts {
   State state;
   Hashtable<LocalityBinding, Set<FlatNode>> treadmap;
   Hashtable<LocalityBinding, Set<TempFlatPair>> transreadmap;
+  Hashtable<LocalityBinding, Set<FlatNode>> twritemap;
+  Hashtable<LocalityBinding, Set<TempFlatPair>> writemap;
   Hashtable<LocalityBinding, Set<FlatNode>> srcmap;
   Hashtable<LocalityBinding, Set<FlatNode>> leftsrcmap;
   Hashtable<LocalityBinding, Set<FlatNode>> rightsrcmap;
@@ -28,8 +31,9 @@ public class DiscoverConflicts {
   Hashtable<LocalityBinding, Hashtable<FlatNode, Hashtable<TempDescriptor, Set<TempFlatPair>>>> lbtofnmap;
   boolean inclusive=false;
   boolean normalassign=false;
+  GlobalFieldType gft;
 
-  public DiscoverConflicts(LocalityAnalysis locality, State state, TypeAnalysis typeanalysis) {
+  public DiscoverConflicts(LocalityAnalysis locality, State state, TypeAnalysis typeanalysis, GlobalFieldType gft) {
     this.locality=locality;
     this.fields=new HashSet<FieldDescriptor>();
     this.arrays=new HashSet<TypeDescriptor>();
@@ -41,9 +45,14 @@ public class DiscoverConflicts {
     leftsrcmap=new Hashtable<LocalityBinding, Set<FlatNode>>();
     rightsrcmap=new Hashtable<LocalityBinding, Set<FlatNode>>();
     lbtofnmap=new Hashtable<LocalityBinding, Hashtable<FlatNode, Hashtable<TempDescriptor, Set<TempFlatPair>>>>();
+    if (gft!=null) {
+      twritemap=new Hashtable<LocalityBinding, Set<FlatNode>>();
+      writemap=new Hashtable<LocalityBinding, Set<TempFlatPair>>();
+    }
+    this.gft=gft;
   }
 
-  public DiscoverConflicts(LocalityAnalysis locality, State state, TypeAnalysis typeanalysis, Hashtable<LocalityBinding, HashSet<FlatNode>> cannotdelaymap, boolean inclusive, boolean normalassign) {
+  public DiscoverConflicts(LocalityAnalysis locality, State state, TypeAnalysis typeanalysis, Hashtable<LocalityBinding, HashSet<FlatNode>> cannotdelaymap, boolean inclusive, boolean normalassign, GlobalFieldType gft) {
     this.locality=locality;
     this.fields=new HashSet<FieldDescriptor>();
     this.arrays=new HashSet<TypeDescriptor>();
@@ -58,6 +67,11 @@ public class DiscoverConflicts {
     lbtofnmap=new Hashtable<LocalityBinding, Hashtable<FlatNode, Hashtable<TempDescriptor, Set<TempFlatPair>>>>();
     this.inclusive=inclusive;
     this.normalassign=normalassign;
+    if (gft!=null) {
+      twritemap=new Hashtable<LocalityBinding, Set<FlatNode>>();
+      writemap=new Hashtable<LocalityBinding, Set<TempFlatPair>>();
+    }
+    this.gft=gft;
   }
 
   public Set<FieldDescriptor> getFields() {
@@ -94,6 +108,15 @@ public class DiscoverConflicts {
       set.add(tfp.f);
     }
     treadmap.put(lb, set);
+    if (gft!=null) {
+      //need to translate write map set
+      set=new HashSet<FlatNode>();
+      for(Iterator<TempFlatPair> it=writemap.get(lb).iterator();it.hasNext();) {
+       TempFlatPair tfp=it.next();
+       set.add(tfp.f);
+      }
+      twritemap.put(lb, set);
+    }
   }
 
   //We have a set of things we write to, figure out what things this
@@ -145,6 +168,10 @@ public class DiscoverConflicts {
     return treadmap.get(lb).contains(fn);
   }
 
+  public boolean getNeedWriteTrans(LocalityBinding lb, FlatNode fn) {
+    return twritemap.get(lb).contains(fn);
+  }
+
   public Hashtable<FlatNode, Hashtable<TempDescriptor, Set<TempFlatPair>>> getMap(LocalityBinding lb) {
     return lbtofnmap.get(lb);
   }
@@ -152,9 +179,19 @@ public class DiscoverConflicts {
   private void analyzeLocality(LocalityBinding lb) {
     MethodDescriptor md=lb.getMethod();
     FlatMethod fm=state.getMethodFlat(md);
+
+    //Compute map from flatnode -> (temps -> source of value)
     Hashtable<FlatNode, Hashtable<TempDescriptor, Set<TempFlatPair>>> fnmap=computeTempSets(lb);
     lbtofnmap.put(lb,fnmap);
-    HashSet<TempFlatPair> tfset=computeTranslationSet(lb, fm, fnmap);
+    HashSet<TempFlatPair> writeset=null;
+    if (gft!=null) {
+      writeset=new HashSet<TempFlatPair>();
+    }
+    HashSet<TempFlatPair> tfset=computeTranslationSet(lb, fm, fnmap, writeset);
+    if (gft!=null) {
+      writemap.put(lb, writeset);
+    }
+    
     HashSet<FlatNode> srctrans=new HashSet<FlatNode>();
     HashSet<FlatNode> leftsrctrans=new HashSet<FlatNode>();
     HashSet<FlatNode> rightsrctrans=new HashSet<FlatNode>();
@@ -164,7 +201,6 @@ public class DiscoverConflicts {
     rightsrcmap.put(lb,rightsrctrans);
 
     //compute writes that need translation on source
-
     for(Iterator<FlatNode> fnit=fm.getNodeSet().iterator();fnit.hasNext();) {
       FlatNode fn=fnit.next();
       Hashtable<FlatNode, Integer> atomictable=locality.getAtomic(lb);
@@ -284,6 +320,76 @@ public class DiscoverConflicts {
     return fn.kind()==FKind.FlatCall||fn.kind()==FKind.FlatMethod;
   }
 
+  private void computeReadOnly(LocalityBinding lb, Hashtable<FlatNode, Set<TypeDescriptor>> updatedtypemap, Hashtable<FlatNode, Set<FieldDescriptor>> updatedfieldmap) {
+    //inside of transaction, try to convert rw access to ro access
+    MethodDescriptor md=lb.getMethod();
+    FlatMethod fm=state.getMethodFlat(md);
+    Hashtable<FlatNode, Integer> atomictable=locality.getAtomic(lb);
+
+    HashSet<FlatNode> toanalyze=new HashSet<FlatNode>();
+    toanalyze.addAll(fm.getNodeSet());
+    
+    while(!toanalyze.isEmpty()) {
+      FlatNode fn=toanalyze.iterator().next();
+      toanalyze.remove(fn);
+      HashSet<TypeDescriptor> updatetypeset=new HashSet<TypeDescriptor>();
+      HashSet<FieldDescriptor> updatefieldset=new HashSet<FieldDescriptor>();
+      
+      //Stop if we aren't in a transaction
+      if (atomictable.get(fn).intValue()==0)
+       continue;
+      
+      //Do merge of all exits
+      for(int i=0;i<fn.numNext();i++) {
+       FlatNode fnnext=fn.getNext(i);
+       if (updatedtypemap.containsKey(fnnext)) {
+         updatetypeset.addAll(updatedtypemap.get(fnnext));
+       }
+       if (updatedfieldmap.containsKey(fnnext)) {
+         updatefieldset.addAll(updatedfieldmap.get(fnnext));
+       }
+      }
+      
+      //process this node
+      if (cannotdelaymap!=null&&cannotdelaymap.containsKey(lb)&&cannotdelaymap.get(lb).contains(fn)!=inclusive) {
+       switch(fn.kind()) {
+       case FKind.FlatSetFieldNode: {
+         FlatSetFieldNode fsfn=(FlatSetFieldNode)fn;
+         updatefieldset.add(fsfn.getField());
+         break;
+       }
+       case FKind.FlatSetElementNode: {
+         FlatSetElementNode fsen=(FlatSetElementNode)fn;
+         updatetypeset.addAll(typeanalysis.expand(fsen.getDst().getType()));
+         break;
+       }
+       case FKind.FlatCall: {
+         FlatCall fcall=(FlatCall)fn;
+         MethodDescriptor mdfc=fcall.getMethod();
+         
+         //get modified fields
+         Set<FieldDescriptor> fields=gft.getFieldsAll(mdfc);
+         updatefieldset.addAll(fields);
+         
+         //get modified arrays
+         Set<TypeDescriptor> arrays=gft.getArraysAll(mdfc);
+         updatetypeset.addAll(typeanalysis.expandSet(arrays));
+         break;
+       }
+       }
+      }
+      
+      if (!updatedtypemap.containsKey(fn)||!updatedfieldmap.containsKey(fn)||
+         !updatedtypemap.get(fn).equals(updatetypeset)||!updatedfieldmap.get(fn).equals(updatefieldset)) {
+       updatedtypemap.put(fn, updatetypeset);
+       updatedfieldmap.put(fn, updatefieldset);
+       for(int i=0;i<fn.numPrev();i++) {
+         toanalyze.add(fn.getPrev(i));
+       }
+      }
+    }
+  }
+
 
   /** Need to figure out which nodes need a transread to make local
   copies.  Transread conceptually tracks conflicts.  This depends on
@@ -291,18 +397,27 @@ public class DiscoverConflicts {
   access fields...If these accesses could conflict, we mark the source
   tempflat pair as needing a transread */
 
-  HashSet<TempFlatPair> computeTranslationSet(LocalityBinding lb, FlatMethod fm, Hashtable<FlatNode, Hashtable<TempDescriptor, Set<TempFlatPair>>> fnmap) {
+  
+  HashSet<TempFlatPair> computeTranslationSet(LocalityBinding lb, FlatMethod fm, Hashtable<FlatNode, Hashtable<TempDescriptor, Set<TempFlatPair>>> fnmap, Set<TempFlatPair> writeset) {
     HashSet<TempFlatPair> tfset=new HashSet<TempFlatPair>();
 
+    //Compute maps from flatnodes -> sets of things that may be updated after this node
+    Hashtable<FlatNode, Set<TypeDescriptor>> updatedtypemap=null;
+    Hashtable<FlatNode, Set<FieldDescriptor>> updatedfieldmap=null;
+
+    if (writeset!=null&&!lb.isAtomic()) {
+      updatedtypemap=new Hashtable<FlatNode, Set<TypeDescriptor>>();
+      updatedfieldmap=new Hashtable<FlatNode, Set<FieldDescriptor>>();
+      computeReadOnly(lb, updatedtypemap, updatedfieldmap);
+    }
+
     for(Iterator<FlatNode> fnit=fm.getNodeSet().iterator();fnit.hasNext();) {
       FlatNode fn=fnit.next();
-
       //Check whether this node matters for cannot delayed computation
       if (cannotdelaymap!=null&&cannotdelaymap.containsKey(lb)&&cannotdelaymap.get(lb).contains(fn)==inclusive)
        continue;
 
       Hashtable<FlatNode, Integer> atomictable=locality.getAtomic(lb);
-
       if (atomictable.get(fn).intValue()>0) {
        Hashtable<TempDescriptor, Set<TempFlatPair>> tmap=fnmap.get(fn);
        switch(fn.kind()) {
@@ -314,6 +429,12 @@ public class DiscoverConflicts {
            if (tfpset!=null)
              tfset.addAll(tfpset);
          }
+         if (updatedtypemap!=null&&updatedtypemap.get(fen).contains(fen.getSrc().getType())) {
+           //this could cause conflict...figure out conflict set
+           Set<TempFlatPair> tfpset=tmap.get(fen.getSrc());
+           if (tfpset!=null)
+             writeset.addAll(tfpset);
+         }
          break;
        }
        case FKind.FlatFieldNode: { 
@@ -324,6 +445,12 @@ public class DiscoverConflicts {
            if (tfpset!=null)
              tfset.addAll(tfpset);
          }
+         if (updatedfieldmap!=null&&updatedfieldmap.get(ffn).contains(ffn.getField())) {
+           //this could cause conflict...figure out conflict set
+           Set<TempFlatPair> tfpset=tmap.get(ffn.getSrc());
+           if (tfpset!=null)
+             writeset.addAll(tfpset);
+         }
          break;
        }
        case FKind.FlatSetFieldNode: { 
@@ -332,6 +459,10 @@ public class DiscoverConflicts {
          Set<TempFlatPair> tfpset=tmap.get(fsfn.getDst());
          if (tfpset!=null)
            tfset.addAll(tfpset);
+         if (writeset!=null) {
+           if (tfpset!=null)
+             writeset.addAll(tfpset);
+         }
          break;
        }
        case FKind.FlatSetElementNode: { 
@@ -340,6 +471,10 @@ public class DiscoverConflicts {
          Set<TempFlatPair> tfpset=tmap.get(fsen.getDst());
          if (tfpset!=null)
            tfset.addAll(tfpset);
+         if (writeset!=null) {
+           if (tfpset!=null)
+             writeset.addAll(tfpset);
+         }
          break;
        }
        case FKind.FlatCall: //assume pessimistically that calls do bad things
@@ -350,6 +485,10 @@ public class DiscoverConflicts {
            Set<TempFlatPair> tfpset=tmap.get(rtmp);
            if (tfpset!=null)
              tfset.addAll(tfpset);
+           if (writeset!=null) {
+             if (tfpset!=null)
+               writeset.addAll(tfpset);
+           }
          }
          break;
        }
index 80a21860a5621f4e77e6804319bb9d9f4142587f..8b0b8f671f916830333ee8146f3af98c5569e1c4 100644 (file)
@@ -108,7 +108,7 @@ public class BuildCode {
     }
     if (state.SINGLETM&&state.DCOPTS) {
       TypeAnalysis typeanalysis=new TypeAnalysis(locality, st, typeutil,callgraph);
-      this.dc=new DiscoverConflicts(locality, st, typeanalysis);
+      this.dc=new DiscoverConflicts(locality, st, typeanalysis, null);
       dc.doAnalysis();
     }
     if (state.DELAYCOMP) {
@@ -118,7 +118,8 @@ public class BuildCode {
       delaycomp=new DelayComputation(locality, st, typeanalysis, gft);
       delaycomp.doAnalysis();
       dc=delaycomp.getConflicts();
-      recorddc=new DiscoverConflicts(locality, st, typeanalysis, delaycomp.getCannotDelayMap(), true, true);
+      recorddc=new DiscoverConflicts(locality, st, typeanalysis, delaycomp.getCannotDelayMap(), true, true, null);
+      recorddc.doAnalysis();
     }
 
     if(state.MLP) {
@@ -1582,7 +1583,9 @@ public class BuildCode {
          }
          //turn off write barrier generation
          wb.turnoff();
+         state.SINGLETM=false;
          generateCode(faen, fm, lb, exitset, output, false);
+         state.SINGLETM=true;
          //turn on write barrier generation
          wb.turnon();
          output.println("}\n\n");
@@ -2000,7 +2003,7 @@ public class BuildCode {
                if (recorddc.getNeedTrans(lb, current_node)) {
                  output.println("STOREPTR("+generateTemp(fm, wrtmp,lb)+");");
                } else {
-                 output.println("STOREPTRNOTRANS("+generateTemp(fm, wrtmp,lb)+");");
+                 output.println("STOREPTRNOLOCK("+generateTemp(fm, wrtmp,lb)+");");
                }
              } else {
                output.println("STORE"+wrtmp.getType().getSafeDescriptor()+"("+generateTemp(fm, wrtmp, lb)+");");
@@ -2459,9 +2462,11 @@ public class BuildCode {
       if (state.DSM) {
        output.println("TRANSREAD("+generateTemp(fm, fgcn.getSrc(),lb)+", (unsigned int) "+generateTemp(fm, fgcn.getSrc(),lb)+");");
       } else {
-       if ((dc==null)||dc.getNeedTrans(lb, fgcn)) {
+       if ((dc==null)||!state.READSET&&dc.getNeedTrans(lb, fgcn)||state.READSET&&dc.getNeedWriteTrans(lb, fgcn)) {
          //need to do translation
          output.println("TRANSREAD("+generateTemp(fm, fgcn.getSrc(),lb)+", "+generateTemp(fm, fgcn.getSrc(),lb)+", (void *)("+localsprefixaddr+"));");
+       } else if (state.READSET&&dc.getNeedTrans(lb, fgcn)) {
+         output.println("TRANSREADRD("+generateTemp(fm, fgcn.getSrc(),lb)+", "+generateTemp(fm, fgcn.getSrc(),lb)+");");
        }
       }
     } else {
@@ -2825,9 +2830,13 @@ public class BuildCode {
 
       output.println(dst+"="+ src +"->"+field+ ";");
       if (ffn.getField().getType().isPtr()&&locality.getAtomic(lb).get(ffn).intValue()>0&&
-          ((dc==null)||dc.getNeedTrans(lb, ffn))&&
           locality.getNodePreTempInfo(lb, ffn).get(ffn.getSrc())!=LocalityAnalysis.SCRATCH) {
-       output.println("TRANSREAD("+dst+", "+dst+", (void *) (" + localsprefixaddr + "));");
+       if ((dc==null)||(!state.READSET&&dc.getNeedTrans(lb, ffn))||
+           (state.READSET&&dc.getNeedWriteTrans(lb, ffn))) {
+         output.println("TRANSREAD("+dst+", "+dst+", (void *) (" + localsprefixaddr + "));");
+       } else if (state.READSET&&dc.getNeedTrans(lb, ffn)) {
+         output.println("TRANSREADRD("+dst+", "+dst+");");
+       }
       }
     } else if (state.DSM) {
       Integer status=locality.getNodePreTempInfo(lb,ffn).get(ffn.getSrc());
@@ -2996,9 +3005,12 @@ public class BuildCode {
       output.println(dst +"=(("+ type+"*)(((char *) &("+ generateTemp(fm,fen.getSrc(),lb)+"->___length___))+sizeof(int)))["+generateTemp(fm, fen.getIndex(),lb)+"];");
 
       if (elementtype.isPtr()&&locality.getAtomic(lb).get(fen).intValue()>0&&
-          ((dc==null)||dc.getNeedTrans(lb, fen))&&
           locality.getNodePreTempInfo(lb, fen).get(fen.getSrc())!=LocalityAnalysis.SCRATCH) {
-       output.println("TRANSREAD("+dst+", "+dst+", (void *)(" + localsprefixaddr+"));");
+       if ((dc==null)||!state.READSET&&dc.getNeedTrans(lb, fen)||state.READSET&&dc.getNeedWriteTrans(lb, fen)) {
+         output.println("TRANSREAD("+dst+", "+dst+", (void *)(" + localsprefixaddr+"));");
+       } else if (state.READSET&&dc.getNeedTrans(lb, fen)) {
+         output.println("TRANSREADRD("+dst+", "+dst+");");
+       }
       }
     } else if (state.DSM) {
       Integer status=locality.getNodePreTempInfo(lb,fen).get(fen.getSrc());
@@ -3222,7 +3234,7 @@ public class BuildCode {
   private void generateFlatCastNode(FlatMethod fm, LocalityBinding lb, FlatCastNode fcn, PrintWriter output) {
     /* TODO: Do type check here */
     if (fcn.getType().isArray()) {
-      throw new Error();
+      output.println(generateTemp(fm,fcn.getDst(),lb)+"=(struct ArrayObject *)"+generateTemp(fm,fcn.getSrc(),lb)+";");
     } else if (fcn.getType().isClass())
       output.println(generateTemp(fm,fcn.getDst(),lb)+"=(struct "+fcn.getType().getSafeSymbol()+" *)"+generateTemp(fm,fcn.getSrc(),lb)+";");
     else
index bfbf60a8be81eb672e65766fb833c73d77f7eab1..5ac95d1897d644f48a2e6d059ea076b2ba095d35 100644 (file)
@@ -82,6 +82,7 @@ public class State {
   public static boolean PRINTCRITICALPATH=false;
   public static boolean ABORTREADERS=false;
   public static boolean SINGLETM=false;
+  public static boolean READSET=false;
   public int CORENUM = 1;
   public String structfile;
   public String main;
index 7725f61070dd1d5d36fea460a88f225ef52525f7..f039f652bdf66da007ea26a1c68e183be641fa8f 100644 (file)
@@ -68,7 +68,7 @@ public class TypeDescriptor extends Descriptor {
            name.equals("charwrapper")||
            name.equals("floatwrapper")||
            name.equals("doublewrapper")||
-           name.equals("objectwrapper"));
+           name.equals("Objectwrapper"));
   }
 
   public TypeDescriptor makeArray(State state) {
index c08ee9df747570c44bb96645ce68c3d108f1c4c9..193a09b24db695fa5df69499e114a94f848de0ce 100755 (executable)
@@ -4,6 +4,7 @@ printhelp() {
 echo -robustroot set up the ROBUSTROOT to directory other than default one
 echo -dsm distributed shared memory
 echo -singleTM single machine committing transactions
+echo -readset turn on readset
 echo -stmdebug STM debug
 echo "-stmstats prints single machine commit (stm) statistics for the benchmark"
 echo -fastmemcpy use fast memcpy
@@ -60,6 +61,7 @@ echo -profile build with profile options
 echo "-useio use standard io to output profiling data (should be used together with -raw and -profile), it only works with single core version"
 echo "-enable-assertions execute assert statements during compilation"
 echo -justanalyze exit after compiler analyses complete
+echo -assembly generate assembly
 echo -help help
 }
 
@@ -99,6 +101,7 @@ JAVAFORWARDOPTS=''
 JAVAOPTS=''
 OPTIONALFLAG=false
 EXITAFTERANALYSIS=false
+ASSEMBLY=false
 
 if [[ -z $1 ]]
 then
@@ -115,6 +118,9 @@ exit
 elif [[ $1 = '-justanalyze' ]]
 then
 EXITAFTERANALYSIS=true
+elif [[ $1 = '-assembly' ]]
+then
+ASSEMBLY=true
 elif [[ $1 = '-abortreaders' ]]
 then
 ABORTREADERS=true
@@ -170,6 +176,10 @@ then
 JAVAOPTS="$JAVAOPTS -singleTM"
 EXTRAOPTIONS="$EXTRAOPTIONS -DSTM"
 SINGLETM=true
+elif [[ $1 = '-readset' ]]
+then
+JAVAOPTS="$JAVAOPTS -readset"
+EXTRAOPTIONS="$EXTRAOPTIONS -DREADSET"
 elif [[ $1 = '-stmdebug' ]]
 then
 EXTRAOPTIONS="$EXTRAOPTIONS -DSTMDEBUG"
@@ -684,6 +694,12 @@ then
 EXTRAOPTIONS="$EXTRAOPTIONS -ldmalloc -DDMALLOC"
 fi
 
+if $ASSEMBLY
+then
+gcc -S $INCLUDES $EXTRAOPTIONS -DPRECISE_GC \
+-c tmpbuilddirectory/methods.c -lm
+fi
+
 if $MULTICOREFLAG
 then
 gcc $INCLUDES $EXTRAOPTIONS \
@@ -693,6 +709,7 @@ gcc $INCLUDES $EXTRAOPTIONS -DPRECISE_GC \
 tmpbuilddirectory/methods.c $FILES -lm -o $MAINFILE.bin
 fi
 
+
 fi #!RAWFLAG
 
 exit