[RFC,7/7] Remove builtin tdesc_i386_*_linux

Message ID 1494518105-15412-8-git-send-email-yao.qi@linaro.org
State New, archived
Headers

Commit Message

Yao Qi May 11, 2017, 3:55 p.m. UTC
  With the previous patch, i386-linux target descriptions are dynamically
generated, so don't need all these i386-linux builtin target descriptions.
This patch removes the code in initialize_tdesc_i386_*_linux to create
these tdesc_i386_*_linux.  These initialize_tdesc_i386_*_linux functions
are still kept to call selftests::record_xml to record the builtin
xml file target descriptions are transformed to c files and compiled
into GDB, so that we can verify that target descriptions from these
xml files are tested (see changes in xml_builtin_tdesc_test).

Function tdesc_print_intiailize_tdesc_p is added to control the scope
of this removal.  In order to highlight the change, I don't re-indent
the code.

gdb:

2017-05-09  Yao Qi  <yao.qi@linaro.org>

	* target-descriptions.c (tdesc_print_intiailize_tdesc_p): New
	function.
	(maint_print_c_tdesc_cmd): Call tdesc_print_intiailize_tdesc_p
	and print code differently.
	(xml_files): New.
	(selftests::record_xml): New function.
	(selftests::xml_builtin_tdesc_test): Add test.
	* target-descriptions.h (selftests::record_xml): Declare.
	* features/i386/i386-avx-avx512-linux.c: Regenerated.
	* features/i386/i386-avx-linux.c: Regenerated.
	* features/i386/i386-avx-mpx-avx512-pku-linux.c: Regenerated.
	* features/i386/i386-avx-mpx-linux.c: Regenerated.
	* features/i386/i386-linux.c: Regenerated.
	* features/i386/i386-mmx-linux.c: Regenerated.
	* features/i386/i386-mpx-linux.c: Regenerated.
---
 gdb/features/i386/i386-avx-avx512-linux.c         | 18 +-------
 gdb/features/i386/i386-avx-linux.c                | 17 +-------
 gdb/features/i386/i386-avx-mpx-avx512-pku-linux.c | 20 +--------
 gdb/features/i386/i386-avx-mpx-linux.c            | 18 +-------
 gdb/features/i386/i386-linux.c                    | 16 +-------
 gdb/features/i386/i386-mmx-linux.c                | 15 +------
 gdb/features/i386/i386-mpx-linux.c                | 17 +-------
 gdb/target-descriptions.c                         | 50 +++++++++++++++++++++--
 gdb/target-descriptions.h                         |  4 ++
 9 files changed, 58 insertions(+), 117 deletions(-)
  

Comments

Philipp Rudo May 16, 2017, 12:02 p.m. UTC | #1
Hi Yao,

On Thu, 11 May 2017 16:55:05 +0100
Yao Qi <qiyaoltc@gmail.com> wrote:

[...]

> diff --git a/gdb/features/i386/i386-mpx-linux.c
> b/gdb/features/i386/i386-mpx-linux.c index 9c722cf..15a1ebe 100644
> --- a/gdb/features/i386/i386-mpx-linux.c
> +++ b/gdb/features/i386/i386-mpx-linux.c
> @@ -216,25 +216,10 @@ create_feature_org_gnu_gdb_i386_mpx (struct target_desc
> *result, long regnum) }
>  #endif /* _ORG_GNU_GDB_I386_MPX */
> 
> -struct target_desc *tdesc_i386_mpx_linux;
>  static void
>  initialize_tdesc_i386_mpx_linux (void)
>  {
> -  struct target_desc *result = allocate_target_description ();
> -
> -  set_tdesc_architecture (result, bfd_scan_arch ("i386"));
> -
> -  set_tdesc_osabi (result, osabi_from_tdesc_string ("GNU/Linux"));
> -
> -  long regnum = 0;
> -
> -  regnum = create_feature_org_gnu_gdb_i386_core_i386 (result, regnum);
> -  regnum = create_feature_org_gnu_gdb_i386_sse (result, regnum);
> -  regnum = create_feature_org_gnu_gdb_i386_linux (result, regnum);
> -  regnum = create_feature_org_gnu_gdb_i386_mpx (result, regnum);
> -
> -  tdesc_i386_mpx_linux = result;
>  #if GDB_SELF_TEST
> -  selftests::record_xml_tdesc ("i386/i386-mpx-linux.xml",
> tdesc_i386_mpx_linux);
> +  selftests::record_xml ("i386/i386-mpx-linux.xml");
>  #endif /* GDB_SELF_TEST */
>  }

Do you really still need the initialize_tdesc_* functions when you initialize
the target description dynamically? Can't you move the seftest somewhere else
and get rid of it in this case?  This would make the code slightly nicer.

> diff --git a/gdb/target-descriptions.c b/gdb/target-descriptions.c
> index a81ae0d..da44e37 100644
> --- a/gdb/target-descriptions.c
> +++ b/gdb/target-descriptions.c
> @@ -2005,6 +2005,15 @@ tdesc_feature_unique_name (const struct tdesc_feature*
> feature) return name;
>  }
> 
> +/* Whether print code initializing tdesc_FUNCTION or not.  */
> +
> +static bool
> +tdesc_print_intiailize_tdesc_p (const char *function)
> +{
> +  return !(strncmp (function, "i386", 4) == 0

common/common-utils.h:startswith (function, "i386") ?

> +	   && strncmp (&function[strlen (function) - 6], "_linux", 6) == 0);

common/common-utils.h:endswith (function, "_linux") ?
(From my concat_path patch
https://sourceware.org/ml/gdb-patches/2017-03/msg00272.html)

Even nicer would be if you don't check the function name at all but check e.g.
a flag a target must set or use a feature method.  Otherwise you will get an
extremely long and unreadable expression once more targets use this method.

Philipp

> +}
> +
>  static void
>  maint_print_c_tdesc_cmd (char *args, int from_tty)
>  {
> @@ -2262,10 +2271,15 @@ maint_print_c_tdesc_cmd (char *args, int from_tty)
>        printf_unfiltered ("\n");
>      }
> 
> -  printf_unfiltered ("struct target_desc *tdesc_%s;\n", function);
> +  if (tdesc_print_intiailize_tdesc_p (function))
> +    printf_unfiltered ("struct target_desc *tdesc_%s;\n", function);
> +
>    printf_unfiltered ("static void\n");
>    printf_unfiltered ("initialize_tdesc_%s (void)\n", function);
>    printf_unfiltered ("{\n");
> +
> +  if (tdesc_print_intiailize_tdesc_p (function))
> +    {
>    printf_unfiltered
>      ("  struct target_desc *result = allocate_target_description ();\n");
> 
> @@ -2320,10 +2334,20 @@ maint_print_c_tdesc_cmd (char *args, int from_tty)
> 
>    printf_unfiltered ("\n");
>    printf_unfiltered ("  tdesc_%s = result;\n", function);
> +    }
> 
>    printf_unfiltered ("#if GDB_SELF_TEST\n");
> -  printf_unfiltered ("  selftests::record_xml_tdesc (\"%s\", tdesc_%s);\n",
> -		     filename_after_features.data (), function);
> +
> +  if (tdesc_print_intiailize_tdesc_p (function))
> +    {
> +      printf_unfiltered ("  selftests::record_xml_tdesc (\"%s\",
> tdesc_%s);\n",
> +			 filename_after_features.data (), function);
> +    }
> +  else
> +    {
> +      printf_unfiltered ("  selftests::record_xml (\"%s\");\n",
> +			 filename_after_features.data ());
> +    }
>    printf_unfiltered ("#endif /* GDB_SELF_TEST */\n");
>    printf_unfiltered ("}\n");
>  }
> @@ -2341,12 +2365,32 @@ record_xml_tdesc (std::string xml_file, const struct
> target_desc *tdesc) lists.emplace_back (xml_file, tdesc);
>  }
> 
> +/* XML files, which generate C files and compiled into GDB.  */
> +
> +static std::vector<std::string> xml_files;
> +
> +void
> +record_xml (const char *xml_file)
> +{
> +  xml_files.emplace_back (xml_file);
> +}
> +
>  /* Test these GDB builtin target descriptions are identical to these which
>     are generated by the corresponding xml files.  */
> 
>  static void
>  xml_builtin_tdesc_test (void)
>  {
> +  /* Make sure we have a test for every xml target description.  */
> +  for (auto const &xml : xml_files)
> +    {
> +      auto r = std::find_if (std::begin (lists), std::end (lists),
> +			     [&xml](decltype (lists)::const_reference e)
> +			     -> bool {return e.first == xml;});
> +
> +      SELF_CHECK (r != std::end (lists));
> +    }
> +
>    std::string feature_dir (ldirname (__FILE__));
>    struct stat st;
> 
> diff --git a/gdb/target-descriptions.h b/gdb/target-descriptions.h
> index 78416ae..2667bcd 100644
> --- a/gdb/target-descriptions.h
> +++ b/gdb/target-descriptions.h
> @@ -266,6 +266,10 @@ namespace selftests {
> 
>  void record_xml_tdesc (std::string xml_file,
>  		       const struct target_desc *tdesc);
> +
> +/* Record c file generated by XML_FILE is compiled into GDB.  */
> +
> +void record_xml (const char *xml_file);
>  }
>  #endif
>
  
Pedro Alves May 17, 2017, 3:46 p.m. UTC | #2
On 05/11/2017 04:55 PM, Yao Qi wrote:
> 
> Function tdesc_print_intiailize_tdesc_p is added to control the scope

typo: "intiailize"

> 	* target-descriptions.c (tdesc_print_intiailize_tdesc_p): New

Ditto.  (multiple places).

> +static bool
> +tdesc_print_intiailize_tdesc_p (const char *function)

In the code too.

Thanks,
Pedro Alves
  

Patch

diff --git a/gdb/features/i386/i386-avx-avx512-linux.c b/gdb/features/i386/i386-avx-avx512-linux.c
index e1aec3c..2f47a40 100644
--- a/gdb/features/i386/i386-avx-avx512-linux.c
+++ b/gdb/features/i386/i386-avx-avx512-linux.c
@@ -220,26 +220,10 @@  create_feature_org_gnu_gdb_i386_avx512 (struct target_desc *result, long regnum)
 }
 #endif /* _ORG_GNU_GDB_I386_AVX512 */
 
-struct target_desc *tdesc_i386_avx_avx512_linux;
 static void
 initialize_tdesc_i386_avx_avx512_linux (void)
 {
-  struct target_desc *result = allocate_target_description ();
-
-  set_tdesc_architecture (result, bfd_scan_arch ("i386"));
-
-  set_tdesc_osabi (result, osabi_from_tdesc_string ("GNU/Linux"));
-
-  long regnum = 0;
-
-  regnum = create_feature_org_gnu_gdb_i386_core_i386 (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_sse (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_linux (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_avx (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_avx512 (result, regnum);
-
-  tdesc_i386_avx_avx512_linux = result;
 #if GDB_SELF_TEST
-  selftests::record_xml_tdesc ("i386/i386-avx-avx512-linux.xml", tdesc_i386_avx_avx512_linux);
+  selftests::record_xml ("i386/i386-avx-avx512-linux.xml");
 #endif /* GDB_SELF_TEST */
 }
diff --git a/gdb/features/i386/i386-avx-linux.c b/gdb/features/i386/i386-avx-linux.c
index 5645dbf..2e85e0d 100644
--- a/gdb/features/i386/i386-avx-linux.c
+++ b/gdb/features/i386/i386-avx-linux.c
@@ -186,25 +186,10 @@  create_feature_org_gnu_gdb_i386_avx (struct target_desc *result, long regnum)
 }
 #endif /* _ORG_GNU_GDB_I386_AVX */
 
-struct target_desc *tdesc_i386_avx_linux;
 static void
 initialize_tdesc_i386_avx_linux (void)
 {
-  struct target_desc *result = allocate_target_description ();
-
-  set_tdesc_architecture (result, bfd_scan_arch ("i386"));
-
-  set_tdesc_osabi (result, osabi_from_tdesc_string ("GNU/Linux"));
-
-  long regnum = 0;
-
-  regnum = create_feature_org_gnu_gdb_i386_core_i386 (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_sse (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_linux (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_avx (result, regnum);
-
-  tdesc_i386_avx_linux = result;
 #if GDB_SELF_TEST
-  selftests::record_xml_tdesc ("i386/i386-avx-linux.xml", tdesc_i386_avx_linux);
+  selftests::record_xml ("i386/i386-avx-linux.xml");
 #endif /* GDB_SELF_TEST */
 }
diff --git a/gdb/features/i386/i386-avx-mpx-avx512-pku-linux.c b/gdb/features/i386/i386-avx-mpx-avx512-pku-linux.c
index c65b515..433f596 100644
--- a/gdb/features/i386/i386-avx-mpx-avx512-pku-linux.c
+++ b/gdb/features/i386/i386-avx-mpx-avx512-pku-linux.c
@@ -287,28 +287,10 @@  create_feature_org_gnu_gdb_i386_pkeys (struct target_desc *result, long regnum)
 }
 #endif /* _ORG_GNU_GDB_I386_PKEYS */
 
-struct target_desc *tdesc_i386_avx_mpx_avx512_pku_linux;
 static void
 initialize_tdesc_i386_avx_mpx_avx512_pku_linux (void)
 {
-  struct target_desc *result = allocate_target_description ();
-
-  set_tdesc_architecture (result, bfd_scan_arch ("i386"));
-
-  set_tdesc_osabi (result, osabi_from_tdesc_string ("GNU/Linux"));
-
-  long regnum = 0;
-
-  regnum = create_feature_org_gnu_gdb_i386_core_i386 (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_sse (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_linux (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_avx (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_mpx (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_avx512 (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_pkeys (result, regnum);
-
-  tdesc_i386_avx_mpx_avx512_pku_linux = result;
 #if GDB_SELF_TEST
-  selftests::record_xml_tdesc ("i386/i386-avx-mpx-avx512-pku-linux.xml", tdesc_i386_avx_mpx_avx512_pku_linux);
+  selftests::record_xml ("i386/i386-avx-mpx-avx512-pku-linux.xml");
 #endif /* GDB_SELF_TEST */
 }
diff --git a/gdb/features/i386/i386-avx-mpx-linux.c b/gdb/features/i386/i386-avx-mpx-linux.c
index 49d25f2..9ff8f25 100644
--- a/gdb/features/i386/i386-avx-mpx-linux.c
+++ b/gdb/features/i386/i386-avx-mpx-linux.c
@@ -238,26 +238,10 @@  create_feature_org_gnu_gdb_i386_mpx (struct target_desc *result, long regnum)
 }
 #endif /* _ORG_GNU_GDB_I386_MPX */
 
-struct target_desc *tdesc_i386_avx_mpx_linux;
 static void
 initialize_tdesc_i386_avx_mpx_linux (void)
 {
-  struct target_desc *result = allocate_target_description ();
-
-  set_tdesc_architecture (result, bfd_scan_arch ("i386"));
-
-  set_tdesc_osabi (result, osabi_from_tdesc_string ("GNU/Linux"));
-
-  long regnum = 0;
-
-  regnum = create_feature_org_gnu_gdb_i386_core_i386 (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_sse (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_linux (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_avx (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_mpx (result, regnum);
-
-  tdesc_i386_avx_mpx_linux = result;
 #if GDB_SELF_TEST
-  selftests::record_xml_tdesc ("i386/i386-avx-mpx-linux.xml", tdesc_i386_avx_mpx_linux);
+  selftests::record_xml ("i386/i386-avx-mpx-linux.xml");
 #endif /* GDB_SELF_TEST */
 }
diff --git a/gdb/features/i386/i386-linux.c b/gdb/features/i386/i386-linux.c
index 51719c5..50ffbda 100644
--- a/gdb/features/i386/i386-linux.c
+++ b/gdb/features/i386/i386-linux.c
@@ -164,24 +164,10 @@  create_feature_org_gnu_gdb_i386_linux (struct target_desc *result, long regnum)
 }
 #endif /* _ORG_GNU_GDB_I386_LINUX */
 
-struct target_desc *tdesc_i386_linux;
 static void
 initialize_tdesc_i386_linux (void)
 {
-  struct target_desc *result = allocate_target_description ();
-
-  set_tdesc_architecture (result, bfd_scan_arch ("i386"));
-
-  set_tdesc_osabi (result, osabi_from_tdesc_string ("GNU/Linux"));
-
-  long regnum = 0;
-
-  regnum = create_feature_org_gnu_gdb_i386_core_i386 (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_sse (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_linux (result, regnum);
-
-  tdesc_i386_linux = result;
 #if GDB_SELF_TEST
-  selftests::record_xml_tdesc ("i386/i386-linux.xml", tdesc_i386_linux);
+  selftests::record_xml ("i386/i386-linux.xml");
 #endif /* GDB_SELF_TEST */
 }
diff --git a/gdb/features/i386/i386-mmx-linux.c b/gdb/features/i386/i386-mmx-linux.c
index 5141692..fe308ba 100644
--- a/gdb/features/i386/i386-mmx-linux.c
+++ b/gdb/features/i386/i386-mmx-linux.c
@@ -88,23 +88,10 @@  create_feature_org_gnu_gdb_i386_linux (struct target_desc *result, long regnum)
 }
 #endif /* _ORG_GNU_GDB_I386_LINUX */
 
-struct target_desc *tdesc_i386_mmx_linux;
 static void
 initialize_tdesc_i386_mmx_linux (void)
 {
-  struct target_desc *result = allocate_target_description ();
-
-  set_tdesc_architecture (result, bfd_scan_arch ("i386"));
-
-  set_tdesc_osabi (result, osabi_from_tdesc_string ("GNU/Linux"));
-
-  long regnum = 0;
-
-  regnum = create_feature_org_gnu_gdb_i386_core_i386 (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_linux (result, regnum);
-
-  tdesc_i386_mmx_linux = result;
 #if GDB_SELF_TEST
-  selftests::record_xml_tdesc ("i386/i386-mmx-linux.xml", tdesc_i386_mmx_linux);
+  selftests::record_xml ("i386/i386-mmx-linux.xml");
 #endif /* GDB_SELF_TEST */
 }
diff --git a/gdb/features/i386/i386-mpx-linux.c b/gdb/features/i386/i386-mpx-linux.c
index 9c722cf..15a1ebe 100644
--- a/gdb/features/i386/i386-mpx-linux.c
+++ b/gdb/features/i386/i386-mpx-linux.c
@@ -216,25 +216,10 @@  create_feature_org_gnu_gdb_i386_mpx (struct target_desc *result, long regnum)
 }
 #endif /* _ORG_GNU_GDB_I386_MPX */
 
-struct target_desc *tdesc_i386_mpx_linux;
 static void
 initialize_tdesc_i386_mpx_linux (void)
 {
-  struct target_desc *result = allocate_target_description ();
-
-  set_tdesc_architecture (result, bfd_scan_arch ("i386"));
-
-  set_tdesc_osabi (result, osabi_from_tdesc_string ("GNU/Linux"));
-
-  long regnum = 0;
-
-  regnum = create_feature_org_gnu_gdb_i386_core_i386 (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_sse (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_linux (result, regnum);
-  regnum = create_feature_org_gnu_gdb_i386_mpx (result, regnum);
-
-  tdesc_i386_mpx_linux = result;
 #if GDB_SELF_TEST
-  selftests::record_xml_tdesc ("i386/i386-mpx-linux.xml", tdesc_i386_mpx_linux);
+  selftests::record_xml ("i386/i386-mpx-linux.xml");
 #endif /* GDB_SELF_TEST */
 }
diff --git a/gdb/target-descriptions.c b/gdb/target-descriptions.c
index a81ae0d..da44e37 100644
--- a/gdb/target-descriptions.c
+++ b/gdb/target-descriptions.c
@@ -2005,6 +2005,15 @@  tdesc_feature_unique_name (const struct tdesc_feature* feature)
   return name;
 }
 
+/* Whether print code initializing tdesc_FUNCTION or not.  */
+
+static bool
+tdesc_print_intiailize_tdesc_p (const char *function)
+{
+  return !(strncmp (function, "i386", 4) == 0
+	   && strncmp (&function[strlen (function) - 6], "_linux", 6) == 0);
+}
+
 static void
 maint_print_c_tdesc_cmd (char *args, int from_tty)
 {
@@ -2262,10 +2271,15 @@  maint_print_c_tdesc_cmd (char *args, int from_tty)
       printf_unfiltered ("\n");
     }
 
-  printf_unfiltered ("struct target_desc *tdesc_%s;\n", function);
+  if (tdesc_print_intiailize_tdesc_p (function))
+    printf_unfiltered ("struct target_desc *tdesc_%s;\n", function);
+
   printf_unfiltered ("static void\n");
   printf_unfiltered ("initialize_tdesc_%s (void)\n", function);
   printf_unfiltered ("{\n");
+
+  if (tdesc_print_intiailize_tdesc_p (function))
+    {
   printf_unfiltered
     ("  struct target_desc *result = allocate_target_description ();\n");
 
@@ -2320,10 +2334,20 @@  maint_print_c_tdesc_cmd (char *args, int from_tty)
 
   printf_unfiltered ("\n");
   printf_unfiltered ("  tdesc_%s = result;\n", function);
+    }
 
   printf_unfiltered ("#if GDB_SELF_TEST\n");
-  printf_unfiltered ("  selftests::record_xml_tdesc (\"%s\", tdesc_%s);\n",
-		     filename_after_features.data (), function);
+
+  if (tdesc_print_intiailize_tdesc_p (function))
+    {
+      printf_unfiltered ("  selftests::record_xml_tdesc (\"%s\", tdesc_%s);\n",
+			 filename_after_features.data (), function);
+    }
+  else
+    {
+      printf_unfiltered ("  selftests::record_xml (\"%s\");\n",
+			 filename_after_features.data ());
+    }
   printf_unfiltered ("#endif /* GDB_SELF_TEST */\n");
   printf_unfiltered ("}\n");
 }
@@ -2341,12 +2365,32 @@  record_xml_tdesc (std::string xml_file, const struct target_desc *tdesc)
   lists.emplace_back (xml_file, tdesc);
 }
 
+/* XML files, which generate C files and compiled into GDB.  */
+
+static std::vector<std::string> xml_files;
+
+void
+record_xml (const char *xml_file)
+{
+  xml_files.emplace_back (xml_file);
+}
+
 /* Test these GDB builtin target descriptions are identical to these which
    are generated by the corresponding xml files.  */
 
 static void
 xml_builtin_tdesc_test (void)
 {
+  /* Make sure we have a test for every xml target description.  */
+  for (auto const &xml : xml_files)
+    {
+      auto r = std::find_if (std::begin (lists), std::end (lists),
+			     [&xml](decltype (lists)::const_reference e)
+			     -> bool {return e.first == xml;});
+
+      SELF_CHECK (r != std::end (lists));
+    }
+
   std::string feature_dir (ldirname (__FILE__));
   struct stat st;
 
diff --git a/gdb/target-descriptions.h b/gdb/target-descriptions.h
index 78416ae..2667bcd 100644
--- a/gdb/target-descriptions.h
+++ b/gdb/target-descriptions.h
@@ -266,6 +266,10 @@  namespace selftests {
 
 void record_xml_tdesc (std::string xml_file,
 		       const struct target_desc *tdesc);
+
+/* Record c file generated by XML_FILE is compiled into GDB.  */
+
+void record_xml (const char *xml_file);
 }
 #endif