Add half-hearted support for Clang, because its `blocks' are deficient. master
authorMark Wooding <mdw@distorted.org.uk>
Sun, 30 Apr 2023 16:15:23 +0000 (17:15 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sun, 30 Apr 2023 18:59:12 +0000 (19:59 +0100)
FINALLY.3
finally-test.c
finally.h
m4/finally.m4

index a7de5fc..1a689c6 100644 (file)
--- a/FINALLY.3
+++ b/FINALLY.3
@@ -133,6 +133,27 @@ Use the GNU C Compiler's support for nested functions and the
 function attribute.  Note that this use of nested functions does not
 involve the use of trampolines, and does not require an executable
 stack.  (This is verified as part of the package tests.)
+.TP
+.B CLANG_BLOCKS
+Use the LLVM Clang compiler's support for `blocks' and the
+.B cleanup
+function attribute.  Depending on the environment, this may require the
+.B \-fblocks
+compiler option, and linking against the
+.B \-lBlocksRuntime
+library.
+.IP
+Support is only partial.  When the statement is executed on exit from
+its scope, it sees the variable values that were current when the
+.B FINALLY
+declaration was processed, and not any changes since.  As a result of
+this bug, the
+.B <finally.h>
+header will fail unless the macro
+.B FINALLY_TOLERATE_BUG_CAPTURE_COPIES
+is defined to a nonzero value, and will set
+.B FINALLY_BUG_CAPTURE_COPIES
+for your program to act on.
 .PP
 If
 .B FINALLY_CONFIG_FLAVOUR
@@ -157,6 +178,9 @@ file's internal documentation for the details.
 .
 .SH BUGS
 The selection of supported compilers is extremely limited.
+.PP
+Clang doesn't work properly because its `blocks' capture a copy of the
+outer scope's variables, rather than closing over them properly.
 .
 .SH AUTHOR
 Mark Wooding, <mdw@distorted.org.uk>
index e95f16b..d33ff70 100644 (file)
@@ -36,6 +36,7 @@
 
 #include <stdio.h>
 
+#define FINALLY_TOLERATE_BUG_CAPTURE_COPIES 1
 #include "finally.h"
 #include "finally-test.h"
 
@@ -59,6 +60,7 @@ static int test_ordering(void)
   return (3);
 }
 
+#ifndef FINALLY_BUG_CAPTURE_COPIES
 static int test_capture(void)
 {
   int n = -1; FINALLY({ STEP(n); });
@@ -66,6 +68,7 @@ static int test_capture(void)
   STEP(0);
   n = 1; return (2);
 }
+#endif
 
 static int test_internal_block(void)
 {
@@ -146,7 +149,11 @@ int main(void)
   RUNTEST(softball);
   RUNTEST(ordering);
   RUNTEST(local_xfer);
+#ifndef FINALLY_BUG_CAPTURE_COPIES
   RUNTEST(capture);
+#else
+  SKIPTEST(capture, "selected flavour captures copies");
+#endif
   RUNTEST(internal_block);
 #if defined(HAVE_FEXCEPTIONS)
   RUNTEST(try_catch);
index 8488161..7b43d38 100644 (file)
--- a/finally.h
+++ b/finally.h
 /* If configuration machinery hasn't determined a flavour, then we'll take a
  * rough guess based on compiler versions.
  */
-#if !defined(FINALLY_CONFIG_FLAVOUR) && \
-       !defined(__clang__) && FINALLY__GCC_P(3, 3)
+#if !defined(FINALLY_CONFIG_FLAVOUR) && defined(__clang__)
+#  if FINALLY__CLANG_P(2, 5)
+#    ifndef __BLOCKS__
+#      error "Clang detected, but blocks support is not available.  \
+This isn't going to work.  Try setting the `-fblocks' compiler option."
+#    endif
+#    define FINALLY_CONFIG_FLAVOUR CLANG_BLOCKS
+#  else
+#    define FINALLY_CONFIG_FLAVOUR NIL
+#  endif
+#endif
+
+#if !defined(FINALLY_CONFIG_FLAVOUR) && FINALLY__GCC_P(3, 3)
 #  define FINALLY_CONFIG_FLAVOUR GCC_NESTED_FUNCTIONS
 #endif
 
 
 /* Before we start, a note about compatibility.  We're using pretty esoteric
  * compiler features here, and not all compilers support them.  I'm most
- * interested in GCC, which will work fine.  This isn't going to work for
- * other compilers, but lots of them try to impersonate GCC, and it's just
- * not worth the effort to try to see through their lies.
+ * interested in GCC, which will work fine, and I'm just a tiny bit
+ * interested in Clang, so there's support for that too.  This isn't going to
+ * work for other compilers, but lots of them try to impersonate GCC, and
+ * it's just not worth the effort to try to see through their lies.
  *
  * So the rules are: if you include this header file, you've either already
  * made an effort to check that it's likely to work (e.g., by using the
 /* Flavour selection machinery. */
 #define FINALLY__FLAVOUR_NIL -1
 #define FINALLY__FLAVOUR_GCC_NESTED_FUNCTIONS 1
+#define FINALLY__FLAVOUR_CLANG_BLOCKS 2
 
 #define FINALLY__SELECTED_FLAVOUR                                      \
        FINALLY__GLUE(FINALLY__FLAVOUR_, FINALLY_CONFIG_FLAVOUR)
          int FINALLY__TMP(tag##__var)
 
 #elif FINALLY__GLUE(FINALLY__FLAVOUR_, FINALLY_CONFIG_FLAVOUR) == \
+       FINALLY__FLAVOUR_CLANG_BLOCKS
+   /* We're being compiled by Clang, so we're messing with the ugly `blocks'
+    * syntax.  Unfortunately, blocks capture names from their outer
+    * environment by copying rather than by reference, so a `FINALLY' block
+    * is insensitive to changes to variables since its establishment.  As a
+    * result of this, we declare a bug.
+    */
+
+#  define FINALLY_BUG_CAPTURE_COPIES 1
+
+   /* We'll need a separate cleanup handler, because we're not allowed to
+    * define a local function to do this.  We'll attach this as the cleanup
+    * handler for the block containing the code that we want to run.
+    */
+   static __inline__ void _finally__runblk(void (^*_blk)(void))
+     { (*_blk)(); }
+
+   /* Now we're ready for the actual macro definition. */
+#  define FINALLY_TAGGED(tag, body)                                    \
+       __attribute__((__unused__, __cleanup__(_finally__runblk)))      \
+         void (^FINALLY__TMP(tag##__blk))(void) = ^{ body }
+
+#elif FINALLY__GLUE(FINALLY__FLAVOUR_, FINALLY_CONFIG_FLAVOUR) == \
        FINALLY__GLUE(FINALLY__FLAVOUR_, NIL)
    /* We don't have a flavour to support this environment. */
 
 #  error "Internal error: `FINALLY_CONFIG_FLAVOUR' bungled."
 #endif
 
+/* Check for bugs. */
+#if defined(FINALLY_BUG_CAPTURE_COPIES) &&                             \
+         (!defined(FINALLY_TOLERATE_BUG_CAPTURE_COPIES) ||             \
+          !FINALLY_TOLERATE_BUG_CAPTURE_COPIES)
+#  error "Implementation captures variables by copying rather than by \
+reference.  Define `FINALLY_BUG_CAPTURE_COPIES' if you don't mind."
+#endif
+
 /* We now have `FINALLY_TAGGED'; defining `FINALLY' is easy.  The TAG here is
  * guaranteed not conflict with any call on `FINALLY_TAGGED', since TAGs are
  * required to be identifiers.
index 70c2d83..d714f2c 100644 (file)
@@ -59,6 +59,20 @@ AC_DEFUN([FINALLY_GCC_ATTRIBUTE_CLEANUP_TEST_PROGRAM], [AC_LANG_PROGRAM([
   __attribute__((cleanup(cleanup_fn))) int x = 0;
   bamboozle(&x);
 ])])
+AC_DEFUN([FINALLY_GCC_REALLY_CLANG_TEST_PROGRAM], [AC_LANG_PROGRAM([], [
+#ifndef __clang__
+  choke me
+#endif
+])])
+AC_DEFUN([FINALLY_CLANG_BLOCKS_TEST_PROGRAM], [AC_LANG_PROGRAM([
+  extern void srand(unsigned); /* might throw? */
+  static __inline__ void runblk(void (^*f)(void)) { (*f)(); }
+], [
+  unsigned x = 1; /* closed over */
+  __attribute__((__unused__, __cleanup__(runblk)))
+    void (^f)(void) = ^{ srand(x); };
+  srand(0);
+])])
 
 dnl Decide whether we can define a plausible `FINALLY' macro.
 AC_DEFUN([FINALLY_CHECK],
@@ -106,6 +120,116 @@ case $finally_flavour,$GCC in
     ;;
 esac
 
+case $finally_flavour,$GCC in
+  undecided,yes)
+    dnl That didn't work.  I guess it's not really GCC after all.  Maybe it's
+    dnl Clang wearing a false moustache.
+
+    AC_CACHE_CHECK([whether the impostor GNU C compiler is really Clang],
+                  [finally_cv_gcc_really_clang_p], [
+      AC_COMPILE_IFELSE([FINALLY_GCC_REALLY_CLANG_TEST_PROGRAM],
+                       [finally_cv_gcc_really_clang_p=yes],
+                       [finally_cv_gcc_really_clang_p=no])])
+    finally_clang_p=$finally_cv_gcc_really_clang_p
+    ;;
+  *)
+    finally_clang_p=no
+    ;;
+esac
+
+case $finally_flavour,$finally_clang_p in
+  undecided,yes)
+    dnl Yup.  Not a particularly convincing disguise, really.  Well, at least
+    dnl Clang has a thing which looks a bit like nested functions, and a bit
+    dnl like closures, only with a terrible syntax, which it calls `blocks'.
+    dnl Unfortunately, we may or may not require varying levels of ceremony
+    dnl to make this work.
+
+    AC_CACHE_CHECK([which hacks are needed to persuade Clang to work with simple blocks],
+                  [finally_cv_clang_blocks_hacks], [
+
+      dnl We'll need to mess with the compiler flags and libraries,
+      dnl so make sure we can put them back again afterwards.
+      finally_original_CFLAGS=$CFLAGS finally_original_LIBS=$LIBS
+
+      dnl Maintain a list of things that we did.  This is the thing
+      dnl we'll cache.
+      unset hacks; win=t
+
+      case $win in
+       t)
+         dnl OK.  First thing, we need to get the compiler proper to accept
+         dnl the `blocks' syntax.  I guess the syntax is so hideous that
+         dnl Clang is sometimes ashamed to admit to parsing it unless we
+         dnl twist its arm.  Apparently `-fblocks' is unnecessary on some
+         dnl targets, so let's see if we can manage without.
+
+         for pass in nil -fblocks; do
+           case $pass in -*) CFLAGS="$CFLAGS -fblocks" ;; esac
+           AC_COMPILE_IFELSE([FINALLY_CLANG_BLOCKS_TEST_PROGRAM],
+                             [win=t], [win=nil])
+           case $win in t) break ;; esac
+         done
+         case $win,$pass in
+           *,nil | nil,*) ;;
+           *) hacks=${hacks+$hacks }$pass
+         esac
+         ;;
+      esac
+
+      case $win in
+       t)
+         dnl We got the compiler to accept the unpleasant syntax.  The next
+         dnl problem is that, technically, the generated code depends on a
+         dnl runtime support library; only our use for these things is so
+         dnl simple that, at reasonable optimization settings, we can do
+         dnl without.  So let's see if we're in that situation.
+
+         for pass in nil -lBlocksRuntime; do
+           case $pass in -l*) LIBS="$LIBS $pass" ;; esac
+           AC_LINK_IFELSE([FINALLY_CLANG_BLOCKS_TEST_PROGRAM],
+                          [win=t], [win=nil])
+           case $win in t) break ;; esac
+         done
+         case $win,$pass in
+           *,nil | nil,*) ;;
+           *) hacks=${hacks+$hacks }$pass
+         esac
+         ;;
+      esac
+
+      dnl We've finished probing, and it's time to report our findings.
+      case $win in
+       t) finally_cv_clang_blocks_hacks=${hacks-none} ;;
+       *) finally_cv_clang_blocks_hacks=failed ;;
+      esac
+
+      dnl Oh!  And don't forget to undo our fiddling with the compiler and
+      dnl linker settings.
+      CFLAGS=$finally_original_CFLAGS LIBS=$finally_original_LIBS])
+
+    dnl That was fun.  Now we know how to persuade Clang to support these
+    dnl block thingummies (or not).  Report our findings.
+    case $finally_cv_clang_blocks_hacks in
+      failed)
+       finally_flavour=NIL
+       ;;
+      *)
+       finally_flavour=CLANG_BLOCKS FINALLY_CFLAGS= FINALLY_LIBS=
+       finally_result="Clang blocks"
+       for hack in $finally_cv_clang_blocks_hacks; do
+         case $hack in
+           none) ;;
+           -l* | -L*) FINALLY_LIBS=${FINALLY_LIBS:+ $FINALLY_LIBS}$hack ;;
+           -*) FINALLY_CFLAGS=${FINALLY_CFLAGS:+ $FINALLY_CFLAGS}$hack ;;
+           *) AC_MSG_ERROR([confused by unexpected hack $hack]) ;;
+         esac
+       done
+       ;;
+    esac
+    ;;
+esac
+
 case $finally_flavour in
   undecided)
     dnl We've got this far and we've drawn a blank.  Give up.