dnl -*-autoconf-*- ### SYNOPSIS ### ### FINALLY([IF-SUCCEEDED], [IF-FAILED]) ### ### DESCRIPTION ### ### Probe at the C compiler to determine how, if at all, to implement the ### `FINALLY' macro, which arranges to run some code when control leaves a ### given scope. This isn't at all a standard C feature, so we need to use ### compiler-specific hacks, and this is the main machinery for deciding ### which hacks to deploy. ### ### On exit, the shell variable `finally_flavour' is set to an uppercase ### word naming the chosen implementation strategy: it will be `NIL' if the ### macro failed and no strategy could be found. The preprocessor define ### `FINALLY_CONFIG_FLAVOUR' is set to `FINALLY_CONFIG_FLAVOUR_...' ### followed by the same word: this is the main input to the selection ### machinery in `finally.h'. ### ### The substitution variables `FINALLY_CFLAGS' and `FINALLY_LIBS' are set ### to any additional compiler flags or libraries needed to support the ### `FINALLY' macro. They can be set per-target in the `Makefile', or ### stuffed into the global variables by the `configure' script. ### ### If the macro managed to find a workable strategy, then the shell ### fragment IF-SUCCEEDED is run; otherwise, (if `finally_flavour' is ### `NIL'), the shell fragment IF-FAILED is run. ### ### LICENSE ### ### Copyright (c) 2023 Mark Wooding ### ### This program is free software: you can redistribute it and/or modify it ### under the terms of the GNU General Public License as published by the ### Free Software Foundation, either version 2 of the License, or (at your ### option) any later version. ### ### This program is distributed in the hope that it will be useful, but ### WITHOUT ANY WARRANTY; without even the implied warranty of ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ### General Public License for more details. ### ### You should have received a copy of the GNU General Public License along ### with this program. If not, see . ### ### In particular, no exception to the GPL is granted regarding generated ### `configure' scripts which are the output of Autoconf. AC_DEFUN([FINALLY_GCC_NESTED_FUNCTIONS_TEST_PROGRAM], [AC_LANG_PROGRAM([], [ __extension__ __inline__ void nested(void) { ; } nested(); ])]) AC_DEFUN([FINALLY_GCC_ATTRIBUTE_CLEANUP_TEST_PROGRAM], [AC_LANG_PROGRAM([ extern void cleanup_fn(const int *x); extern void bamboozle(int *x_inout); ], [ __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], [finally_flavour=undecided finally_result="not supported" dnl We're going to want to test C code. AC_LANG_PUSH([C]) case $finally_flavour,$GCC in undecided,yes) dnl Our GCC-ish strategies have a common factor: they depend on dnl `__attribute__((cleanup(...)))' working. So let's check for that. AC_CACHE_CHECK([whether the alleged GNU C compiler supports \`__attribute__((cleanup(...)))'], [finally_cv_gcc_attribute_cleanup_p], [ AC_COMPILE_IFELSE([FINALLY_GCC_ATTRIBUTE_CLEANUP_TEST_PROGRAM], [finally_cv_gcc_attribute_cleanup_p=yes], [finally_cv_gcc_attribute_cleanup_p=no])]) case $finally_cv_gcc_attribute_cleanup_p in no) finally_flavour=NIL ;; esac ;; esac case $finally_flavour,$GCC in undecided,yes) dnl Autoconf has decided that the compiler smells a bit like GCC, and it dnl certainly seems to support a GCC extension. But many compilers dnl impersonate GCC, in more or less convincing ways. Our GCC-flavoured dnl `FINALLY' code depends on nested functions, which GCC has supported dnl pretty much forever, but other compilers don't even though they lie dnl about being compatible. AC_CACHE_CHECK([whether the alleged GNU C compiler supports nested functions], [finally_cv_gcc_nested_functions_p], [ AC_COMPILE_IFELSE([FINALLY_GCC_NESTED_FUNCTIONS_TEST_PROGRAM], [finally_cv_gcc_nested_functions_p=yes], [finally_cv_gcc_nested_functions_p=no])]) case $finally_cv_gcc_nested_functions_p in yes) finally_flavour=GCC_NESTED_FUNCTIONS finally_result="GCC nested functions" ;; esac ;; 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. finally_flavour=NIL ;; esac AC_LANG_POP([C]) dnl Pass the results on to the implementation machinery. AC_MSG_CHECKING([how to implement deferred cleanup code]) AC_DEFINE_UNQUOTED([FINALLY_CONFIG_FLAVOUR], [$finally_flavour], [Select one of the implementation strategies for the `FINALLY' macro.]) AC_SUBST(FINALLY_CFLAGS) AC_SUBST(FINALLY_LIBS) AC_MSG_RESULT([$finally_result]) dnl Invoke the caller's shell fragments according to our findings. case $finally_flavour in nil) $2 ;; *) $1 ;; esac ])