< prev index next >

src/share/vm/runtime/arguments.cpp

Print this page

        

@@ -245,25 +245,60 @@
       new SystemProperty("java.vm.specification.version", buffer, false));
   PropertyList_add(&_system_properties,
       new SystemProperty("java.vm.vendor", VM_Version::vm_vendor(),  false));
 }
 
+//------------------------------------------------------------------------------
+/*
+ *  -XX argument processing
+ * 
+ * Normal -XX arguments are defined in globals.hpp and are handled here in parse_argument().
+ * 
+ * Over time -XX arguments may change. There are mechanisms to handle common cases:
+ * 
+ *        ALIAS: An option may be renamed or replaced by another option. The old name can be supported by adding 
+ *               the old and new option names to the "aliased_jvm_flags" table. Delete the old variable from globals.hpp.
+ * 
+ *   DEPRECATED: An option may be supported, but a warning is printed to let the user know that support may be removed
+ *               in the future. Both regular and aliased options may be deprecated.
+ *               Add a deprecation warning for an option (or alias) by adding an entry in the "deprecated_jvm_flags" table. 
+ *               Specify the option name, the jdk version that deprecated the option, and the jdk version in that will
+ *               remove the option (if removal has been scheduled).
+ * 
+ *     OBSOLETE: An option may be removed (and deleted from globals.hpp), but still be accepted on the command line.
+ *               A warning is printed to let the user know that support may be removed in the future. 
+ *               Add an obsolete warning for an option (or alias) by adding an entry in the "obsolete_jvm_flags" table. 
+ *               Specify the option name, the jdk version that deprecated the option, and the jdk version in that will
+ *               remove the option (if removal has been scheduled).
+ * 
+ * Tests:  Aliases are tested in VMAliasOptions.java. Deprecated options in VMDeprecatedOptions.java. Obosloete options are tested in various files.
+ */
+
+/**
+ * Declare an obsolete or deprecated -XX flag.
+ */
+typedef struct {
+  const char* name;
+  JDK_Version obsoleted_in; // when the warning started (obsolete or deprecated)
+  JDK_Version accept_until; // which version to start denying the existence (if scheduled)
+  
+  static const uint8_t _removal_unscheduled = 0;
+  
+  bool is_removal_scheduled() const {
+      return accept_until.major_version() != _removal_unscheduled;
+  }
+  
+} SpecialFlag;
+
 /**
- * Provide a slightly more user-friendly way of eliminating -XX flags.
  * When a flag is eliminated, it can be added to this list in order to
  * continue accepting this flag on the command-line, while issuing a warning
  * and ignoring the value.  Once the JDK version reaches the 'accept_until'
  * limit, we flatly refuse to admit the existence of the flag.  This allows
  * a flag to die correctly over JDK releases using HSX.
  */
-typedef struct {
-  const char* name;
-  JDK_Version obsoleted_in; // when the flag went away
-  JDK_Version accept_until; // which version to start denying the existence
-} ObsoleteFlag;
-
-static ObsoleteFlag obsolete_jvm_flags[] = {
+static SpecialFlag const obsolete_jvm_flags[] = {
   { "UseTrainGC",                    JDK_Version::jdk(5), JDK_Version::jdk(7) },
   { "UseSpecialLargeObjectHandling", JDK_Version::jdk(5), JDK_Version::jdk(7) },
   { "UseOversizedCarHandling",       JDK_Version::jdk(5), JDK_Version::jdk(7) },
   { "TraceCarAllocation",            JDK_Version::jdk(5), JDK_Version::jdk(7) },
   { "PrintTrainGCProcessingStats",   JDK_Version::jdk(5), JDK_Version::jdk(7) },

@@ -294,11 +329,12 @@
                            JDK_Version::jdk_update(6,27), JDK_Version::jdk(8) },
   { "AllowTransitionalJSR292",       JDK_Version::jdk(7), JDK_Version::jdk(8) },
   { "UseCompressedStrings",          JDK_Version::jdk(7), JDK_Version::jdk(8) },
   { "CMSPermGenPrecleaningEnabled", JDK_Version::jdk(8),  JDK_Version::jdk(9) },
   { "CMSTriggerPermRatio", JDK_Version::jdk(8),  JDK_Version::jdk(9) },
-  { "CMSInitiatingPermOccupancyFraction", JDK_Version::jdk(8),  JDK_Version::jdk(9) },
+  { "CMSInitiatingPermOccupancyFraction",
+                                     JDK_Version::jdk(8), JDK_Version::jdk(9) },
   { "AdaptivePermSizeWeight", JDK_Version::jdk(8),  JDK_Version::jdk(9) },
   { "PermGenPadding", JDK_Version::jdk(8),  JDK_Version::jdk(9) },
   { "PermMarkSweepDeadRatio", JDK_Version::jdk(8),  JDK_Version::jdk(9) },
   { "PermSize", JDK_Version::jdk(8),  JDK_Version::jdk(9) },
   { "MaxPermSize", JDK_Version::jdk(8),  JDK_Version::jdk(9) },

@@ -313,12 +349,11 @@
   { "UseMPSS",                       JDK_Version::jdk(8), JDK_Version::jdk(9) },
   { "UseStringCache",                JDK_Version::jdk(8), JDK_Version::jdk(9) },
   { "UseOldInlining",                JDK_Version::jdk(9), JDK_Version::jdk(10) },
   { "SafepointPollOffset",           JDK_Version::jdk(9), JDK_Version::jdk(10) },
 #ifdef PRODUCT
-  { "DesiredMethodLimit",
-                           JDK_Version::jdk_update(7, 2), JDK_Version::jdk(8) },
+  { "DesiredMethodLimit",  JDK_Version::jdk_update(7, 2), JDK_Version::jdk(8) },
 #endif // PRODUCT
   { "UseVMInterruptibleIO",          JDK_Version::jdk(8), JDK_Version::jdk(9) },
   { "UseBoundThreads",               JDK_Version::jdk(9), JDK_Version::jdk(10) },
   { "DefaultThreadPriority",         JDK_Version::jdk(9), JDK_Version::jdk(10) },
   { "NoYieldsInMicrolock",           JDK_Version::jdk(9), JDK_Version::jdk(10) },

@@ -336,37 +371,112 @@
 #endif // ZERO
   { "UseCompilerSafepoints",         JDK_Version::jdk(9), JDK_Version::jdk(10) },
   { NULL, JDK_Version(0), JDK_Version(0) }
 };
 
-// Returns true if the flag is obsolete and fits into the range specified
-// for being ignored.  In the case that the flag is ignored, the 'version'
-// value is filled in with the version number when the flag became
-// obsolete so that that value can be displayed to the user.
-bool Arguments::is_newly_obsolete(const char *s, JDK_Version* version) {
+/**
+ * Deprecated flags are like obsolete flags except that they still *do* something. A flag 
+ * in the deprecated_jvm_flags list must either have a corresponding declaration in globals.hpp,
+ * or be an "alias" (see below) for a real flag.
+ * 
+ * Once the JDK version reaches the 'accept_until' limit, we flatly refuse to admit the existence of the flag.
+ * At this point its declaration (if it has one) can be removed from globals.hpp.
+ */
+static SpecialFlag const deprecated_jvm_flags[] = {
+  // deprecated non-alias flags:
+  { "MaxGCMinorPauseMillis",        JDK_Version::jdk(8), JDK_Version::jdk(SpecialFlag::_removal_unscheduled) },
+  { "UseParNewGC",                  JDK_Version::jdk(9), JDK_Version::jdk(10) },
+  
+  // deprecated alias flags (see also aliased_jvm_flags):
+  { "DefaultMaxRAMFraction",        JDK_Version::jdk(8), JDK_Version::jdk(SpecialFlag::_removal_unscheduled) },
+  { "CMSMarkStackSizeMax",          JDK_Version::jdk(9), JDK_Version::jdk(10) },
+  { "CMSMarkStackSize",             JDK_Version::jdk(9), JDK_Version::jdk(10) },
+  { "G1MarkStackSize",              JDK_Version::jdk(9), JDK_Version::jdk(10) },
+  { "ParallelMarkingThreads",       JDK_Version::jdk(9), JDK_Version::jdk(10) },
+  { "ParallelCMSThreads",           JDK_Version::jdk(9), JDK_Version::jdk(10) },
+  { NULL, JDK_Version(0), JDK_Version(0) }
+};
+
+/**
+ * Some flags are actually aliases for other flags. This is often part of the process of
+ * deprecating a flag, but not all aliases need to be deprecated.
+ */
+typedef struct {
+  const char* alias_name;
+  const char* real_name;
+} AliasedFlag;
+
+static AliasedFlag const aliased_jvm_flags[] = {
+  { "DefaultMaxRAMFraction",    "MaxRAMFraction"},
+  { "CMSMarkStackSizeMax",      "MarkStackSizeMax"},
+  { "CMSMarkStackSize",         "MarkStackSize"},
+  { "G1MarkStackSize",          "MarkStackSize"},
+  { "ParallelMarkingThreads",   "ConcGCThreads"},
+  { "ParallelCMSThreads",       "ConcGCThreads"},
+  { NULL, NULL}
+};
+
+// Returns 1 if the flag is special and jdk version is in the range specified.
+//     In this case value is filled in with the version number when the flag became
+//     special so that that value can be displayed to the user.
+// Returns -1 if the flag is special and has expired (should be ignored))
+// Returns 0 if the flag is not special.
+static int is_special_flag(const SpecialFlag special_table[], const char *s, JDK_Version* version) {
   int i = 0;
   assert(version != NULL, "Must provide a version buffer");
-  while (obsolete_jvm_flags[i].name != NULL) {
-    const ObsoleteFlag& flag_status = obsolete_jvm_flags[i];
+  while (special_table[i].name != NULL) {
+    const SpecialFlag& flag_status = special_table[i];
     // <flag>=xxx form
     // [-|+]<flag> form
     size_t len = strlen(flag_status.name);
     if (((strncmp(flag_status.name, s, len) == 0) &&
          (strlen(s) == len)) ||
         ((s[0] == '+' || s[0] == '-') &&
          (strncmp(flag_status.name, &s[1], len) == 0) &&
          (strlen(&s[1]) == len))) {
-      if (JDK_Version::current().compare(flag_status.accept_until) == -1) {
+      if (!flag_status.is_removal_scheduled() ||
+          JDK_Version::current().compare(flag_status.accept_until) == -1) {
           *version = flag_status.obsoleted_in;
-          return true;
+        return 1;
+      } else {
+        return -1;
       }
     }
     i++;
   }
-  return false;
+  return 0;
+}
+
+// Returns true if the flag is obsolete and fits into the range specified
+// for being ignored.  In the case that the flag is ignored, the 'version'
+// buffer is filled in with the version number when the flag became
+// obsolete so that that value can be displayed to the user.
+bool Arguments::is_newly_obsolete(const char *s, JDK_Version* version) {
+  return (is_special_flag(obsolete_jvm_flags, s, version) == 1);
+}
+
+int Arguments::is_deprecated_flag(const char *s, JDK_Version* version) {
+  return is_special_flag(deprecated_jvm_flags, s, version);
+}
+
+// Return the real name for the flag passed on the command line.
+const char* Arguments::real_flag_name(const char *flag_name) {
+  int i = 0;
+  while (aliased_jvm_flags[i].alias_name != NULL) {
+    const AliasedFlag& flag_status = aliased_jvm_flags[i];
+    size_t len = strlen(flag_status.alias_name);
+    if (((strncmp(flag_status.alias_name, flag_name, len) == 0) &&
+         (strlen(flag_name) == len))) {
+        return flag_status.real_name;
+    }
+    i++;
+  }
+  return flag_name;
 }
 
+//------------------------------------------------------------------------------
+
 // Constructs the system class path (aka boot class path) from the following
 // components, in order:
 //
 //     prefix           // from -Xbootclasspath/p:...
 //     base             // from os::get_system_properties() or -Xbootclasspath=

@@ -631,15 +741,15 @@
   default:
     ShouldNotReachHere();
   }
 }
 
-static bool set_bool_flag(char* name, bool value, Flag::Flags origin) {
+static bool set_bool_flag(const char* name, bool value, Flag::Flags origin) {
   return CommandLineFlags::boolAtPut(name, &value, origin);
 }
 
-static bool set_fp_numeric_flag(char* name, char* value, Flag::Flags origin) {
+static bool set_fp_numeric_flag(const char* name, char* value, Flag::Flags origin) {
   double v;
   if (sscanf(value, "%lf", &v) != 1) {
     return false;
   }
 

@@ -647,11 +757,11 @@
     return true;
   }
   return false;
 }
 
-static bool set_numeric_flag(char* name, char* value, Flag::Flags origin) {
+static bool set_numeric_flag(const char* name, char* value, Flag::Flags origin) {
   julong v;
   intx intx_v;
   bool is_neg = false;
   // Check the sign first since atomull() parses only unsigned values.
   if (*value == '-') {

@@ -684,18 +794,18 @@
     return true;
   }
   return false;
 }
 
-static bool set_string_flag(char* name, const char* value, Flag::Flags origin) {
+static bool set_string_flag(const char* name, const char* value, Flag::Flags origin) {
   if (!CommandLineFlags::ccstrAtPut(name, &value, origin))  return false;
   // Contract:  CommandLineFlags always returns a pointer that needs freeing.
   FREE_C_HEAP_ARRAY(char, value);
   return true;
 }
 
-static bool append_to_string_flag(char* name, const char* new_value, Flag::Flags origin) {
+static bool append_to_string_flag(const char* name, const char* new_value, Flag::Flags origin) {
   const char* old_value = "";
   if (!CommandLineFlags::ccstrAt(name, &old_value))  return false;
   size_t old_len = old_value != NULL ? strlen(old_value) : 0;
   size_t new_len = strlen(new_value);
   const char* value;

@@ -719,65 +829,118 @@
     FREE_C_HEAP_ARRAY(char, free_this_too);
   }
   return true;
 }
 
+// For expired deprecated option return null, for aliased option return the real option name, otherwise return "arg".
+const char* Arguments::handle_aliases_and_deprecation(const char* arg) {
+  const char* real_name = real_flag_name(arg);
+  JDK_Version since = JDK_Version();
+  switch (is_deprecated_flag(arg, &since)) {
+    case -1:
+      return NULL;
+    case 0:
+      return real_name;
+    case 1: {
+      char version[256];
+      since.to_string(version, sizeof(version));
+      if (real_name != arg) {
+        warning("option %s was deprecated in version %s and will likely be removed in a future release. Use option %s instead.",
+                arg, version, real_name);
+      } else {
+        warning("option %s was deprecated in version %s and will likely be removed in a future release.",
+                arg, version);
+      }
+      return real_name;
+    }
+  }
+  ShouldNotReachHere();
+  return NULL;
+}
+
 bool Arguments::parse_argument(const char* arg, Flag::Flags origin) {
 
   // range of acceptable characters spelled out for portability reasons
 #define NAME_RANGE  "[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_]"
 #define BUFLEN 255
   char name[BUFLEN+1];
   char dummy;
+  const char* real_name;
 
   if (sscanf(arg, "-%" XSTR(BUFLEN) NAME_RANGE "%c", name, &dummy) == 1) {
-    return set_bool_flag(name, false, origin);
+    real_name = handle_aliases_and_deprecation(name);
+    if (real_name == NULL) {
+      return NULL; // "name" is a deprecated option that has expired.
+    }
+    return set_bool_flag(real_name, false, origin);
   }
   if (sscanf(arg, "+%" XSTR(BUFLEN) NAME_RANGE "%c", name, &dummy) == 1) {
-    return set_bool_flag(name, true, origin);
+    real_name = handle_aliases_and_deprecation(name);
+    if (real_name == NULL) {
+      return NULL;
+    }
+    return set_bool_flag(real_name, true, origin);
   }
 
   char punct;
   if (sscanf(arg, "%" XSTR(BUFLEN) NAME_RANGE "%c", name, &punct) == 2 && punct == '=') {
     const char* value = strchr(arg, '=') + 1;
-    Flag* flag = Flag::find_flag(name, strlen(name));
+    Flag* flag;
+    
+    real_name = handle_aliases_and_deprecation(name);
+    if (real_name == NULL) {
+      return NULL;
+    }
+    flag = Flag::find_flag(real_name, strlen(real_name));
     if (flag != NULL && flag->is_ccstr()) {
       if (flag->ccstr_accumulates()) {
-        return append_to_string_flag(name, value, origin);
+        return append_to_string_flag(real_name, value, origin);
       } else {
         if (value[0] == '\0') {
           value = NULL;
         }
-        return set_string_flag(name, value, origin);
+        return set_string_flag(real_name, value, origin);
       }
     }
   }
 
   if (sscanf(arg, "%" XSTR(BUFLEN) NAME_RANGE ":%c", name, &punct) == 2 && punct == '=') {
     const char* value = strchr(arg, '=') + 1;
     // -XX:Foo:=xxx will reset the string flag to the given value.
     if (value[0] == '\0') {
       value = NULL;
     }
-    return set_string_flag(name, value, origin);
+    real_name = handle_aliases_and_deprecation(name);
+    if (real_name == NULL) {
+      return NULL;
+    }
+    return set_string_flag(real_name, value, origin);
   }
 
 #define SIGNED_FP_NUMBER_RANGE "[-0123456789.]"
 #define SIGNED_NUMBER_RANGE    "[-0123456789]"
 #define        NUMBER_RANGE    "[0123456789]"
   char value[BUFLEN + 1];
   char value2[BUFLEN + 1];
   if (sscanf(arg, "%" XSTR(BUFLEN) NAME_RANGE "=" "%" XSTR(BUFLEN) SIGNED_NUMBER_RANGE "." "%" XSTR(BUFLEN) NUMBER_RANGE "%c", name, value, value2, &dummy) == 3) {
     // Looks like a floating-point number -- try again with more lenient format string
     if (sscanf(arg, "%" XSTR(BUFLEN) NAME_RANGE "=" "%" XSTR(BUFLEN) SIGNED_FP_NUMBER_RANGE "%c", name, value, &dummy) == 2) {
-      return set_fp_numeric_flag(name, value, origin);
+      real_name = handle_aliases_and_deprecation(name);
+      if (real_name == NULL) {
+        return NULL;
+      }
+      return set_fp_numeric_flag(real_name, value, origin);
     }
   }
 
 #define VALUE_RANGE "[-kmgtxKMGTX0123456789abcdefABCDEF]"
   if (sscanf(arg, "%" XSTR(BUFLEN) NAME_RANGE "=" "%" XSTR(BUFLEN) VALUE_RANGE "%c", name, value, &dummy) == 2) {
-    return set_numeric_flag(name, value, origin);
+    real_name = handle_aliases_and_deprecation(name);
+    if (real_name == NULL) {
+      return NULL;
+    }
+    return set_numeric_flag(real_name, value, origin);
   }
 
   return false;
 }
 

@@ -1742,11 +1905,10 @@
   } else if (UseConcMarkSweepGC) {
     set_cms_and_parnew_gc_flags();
   } else if (UseG1GC) {
     set_g1_gc_flags();
   }
-  check_deprecated_gc_flags();
   if (AssumeMP && !UseSerialGC) {
     if (FLAG_IS_DEFAULT(ParallelGCThreads) && ParallelGCThreads == 1) {
       warning("If the number of processors is expected to increase from one, then"
               " you should configure the number of parallel GC threads appropriately"
               " using -XX:ParallelGCThreads=N");

@@ -1772,15 +1934,10 @@
 
 // Use static initialization to get the default before parsing
 static const uintx DefaultHeapBaseMinAddress = HeapBaseMinAddress;
 
 void Arguments::set_heap_size() {
-  if (!FLAG_IS_DEFAULT(DefaultMaxRAMFraction)) {
-    // Deprecated flag
-    FLAG_SET_CMDLINE(uintx, MaxRAMFraction, DefaultMaxRAMFraction);
-  }
-
   const julong phys_mem =
     FLAG_IS_DEFAULT(MaxRAM) ? MIN2(os::physical_memory(), (julong)MaxRAM)
                             : (julong)MaxRAM;
 
   // If the maximum heap size has not been set with -Xmx,

@@ -2134,24 +2291,10 @@
   }
 
   return true;
 }
 
-void Arguments::check_deprecated_gc_flags() {
-  if (FLAG_IS_CMDLINE(UseParNewGC)) {
-    warning("The UseParNewGC flag is deprecated and will likely be removed in a future release");
-  }
-  if (FLAG_IS_CMDLINE(MaxGCMinorPauseMillis)) {
-    warning("Using MaxGCMinorPauseMillis as minor pause goal is deprecated"
-            "and will likely be removed in future release");
-  }
-  if (FLAG_IS_CMDLINE(DefaultMaxRAMFraction)) {
-    warning("DefaultMaxRAMFraction is deprecated and will likely be removed in a future release. "
-        "Use MaxRAMFraction instead.");
-  }
-}
-
 // Check stack pages settings
 bool Arguments::check_stack_pages()
 {
   bool status = true;
   status = status && verify_min_value(StackYellowPages, 1, "StackYellowPages");

@@ -2384,11 +2527,10 @@
   status = status && verify_min_value(ParGCStridesPerThread, 1, "ParGCStridesPerThread");
 
   status = status && verify_min_value(MinRAMFraction, 1, "MinRAMFraction");
   status = status && verify_min_value(InitialRAMFraction, 1, "InitialRAMFraction");
   status = status && verify_min_value(MaxRAMFraction, 1, "MaxRAMFraction");
-  status = status && verify_min_value(DefaultMaxRAMFraction, 1, "DefaultMaxRAMFraction");
 
   status = status && verify_interval(AdaptiveTimeWeight, 0, 100, "AdaptiveTimeWeight");
   status = status && verify_min_value(AdaptiveSizeDecrementScaleFactor, 1, "AdaptiveSizeDecrementScaleFactor");
 
   status = status && verify_interval(TLABAllocationWeight, 0, 100, "TLABAllocationWeight");

@@ -3221,50 +3363,10 @@
     } else if (match_option(option, "-XX:+FullGCALot")) {
       FLAG_SET_CMDLINE(bool, FullGCALot, true);
       // disable scavenge before parallel mark-compact
       FLAG_SET_CMDLINE(bool, ScavengeBeforeFullGC, false);
 #endif
-    } else if (match_option(option, "-XX:CMSMarkStackSize=", &tail) ||
-               match_option(option, "-XX:G1MarkStackSize=", &tail)) {
-      julong stack_size = 0;
-      ArgsRange errcode = parse_memory_size(tail, &stack_size, 1);
-      if (errcode != arg_in_range) {
-        jio_fprintf(defaultStream::error_stream(),
-                    "Invalid mark stack size: %s\n", option->optionString);
-        describe_range_error(errcode);
-        return JNI_EINVAL;
-      }
-      jio_fprintf(defaultStream::error_stream(),
-        "Please use -XX:MarkStackSize in place of "
-        "-XX:CMSMarkStackSize or -XX:G1MarkStackSize in the future\n");
-      FLAG_SET_CMDLINE(uintx, MarkStackSize, stack_size);
-    } else if (match_option(option, "-XX:CMSMarkStackSizeMax=", &tail)) {
-      julong max_stack_size = 0;
-      ArgsRange errcode = parse_memory_size(tail, &max_stack_size, 1);
-      if (errcode != arg_in_range) {
-        jio_fprintf(defaultStream::error_stream(),
-                    "Invalid maximum mark stack size: %s\n",
-                    option->optionString);
-        describe_range_error(errcode);
-        return JNI_EINVAL;
-      }
-      jio_fprintf(defaultStream::error_stream(),
-         "Please use -XX:MarkStackSizeMax in place of "
-         "-XX:CMSMarkStackSizeMax in the future\n");
-      FLAG_SET_CMDLINE(uintx, MarkStackSizeMax, max_stack_size);
-    } else if (match_option(option, "-XX:ParallelMarkingThreads=", &tail) ||
-               match_option(option, "-XX:ParallelCMSThreads=", &tail)) {
-      uintx conc_threads = 0;
-      if (!parse_uintx(tail, &conc_threads, 1)) {
-        jio_fprintf(defaultStream::error_stream(),
-                    "Invalid concurrent threads: %s\n", option->optionString);
-        return JNI_EINVAL;
-      }
-      jio_fprintf(defaultStream::error_stream(),
-        "Please use -XX:ConcGCThreads in place of "
-        "-XX:ParallelMarkingThreads or -XX:ParallelCMSThreads in the future\n");
-      FLAG_SET_CMDLINE(uintx, ConcGCThreads, conc_threads);
     } else if (match_option(option, "-XX:MaxDirectMemorySize=", &tail)) {
       julong max_direct_memory_size = 0;
       ArgsRange errcode = parse_memory_size(tail, &max_direct_memory_size, 0);
       if (errcode != arg_in_range) {
         jio_fprintf(defaultStream::error_stream(),
< prev index next >