Add half-hearted support for Clang, because its `blocks' are deficient.
[finally] / m4 / finally.m4
1 dnl -*-autoconf-*-
2
3 ### SYNOPSIS
4 ###
5 ### FINALLY([IF-SUCCEEDED], [IF-FAILED])
6 ###
7 ### DESCRIPTION
8 ###
9 ### Probe at the C compiler to determine how, if at all, to implement the
10 ### `FINALLY' macro, which arranges to run some code when control leaves a
11 ### given scope. This isn't at all a standard C feature, so we need to use
12 ### compiler-specific hacks, and this is the main machinery for deciding
13 ### which hacks to deploy.
14 ###
15 ### On exit, the shell variable `finally_flavour' is set to an uppercase
16 ### word naming the chosen implementation strategy: it will be `NIL' if the
17 ### macro failed and no strategy could be found. The preprocessor define
18 ### `FINALLY_CONFIG_FLAVOUR' is set to `FINALLY_CONFIG_FLAVOUR_...'
19 ### followed by the same word: this is the main input to the selection
20 ### machinery in `finally.h'.
21 ###
22 ### The substitution variables `FINALLY_CFLAGS' and `FINALLY_LIBS' are set
23 ### to any additional compiler flags or libraries needed to support the
24 ### `FINALLY' macro. They can be set per-target in the `Makefile', or
25 ### stuffed into the global variables by the `configure' script.
26 ###
27 ### If the macro managed to find a workable strategy, then the shell
28 ### fragment IF-SUCCEEDED is run; otherwise, (if `finally_flavour' is
29 ### `NIL'), the shell fragment IF-FAILED is run.
30 ###
31 ### LICENSE
32 ###
33 ### Copyright (c) 2023 Mark Wooding <mdw@distorted.org.uk>
34 ###
35 ### This program is free software: you can redistribute it and/or modify it
36 ### under the terms of the GNU General Public License as published by the
37 ### Free Software Foundation, either version 2 of the License, or (at your
38 ### option) any later version.
39 ###
40 ### This program is distributed in the hope that it will be useful, but
41 ### WITHOUT ANY WARRANTY; without even the implied warranty of
42 ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
43 ### General Public License for more details.
44 ###
45 ### You should have received a copy of the GNU General Public License along
46 ### with this program. If not, see <http://www.gnu.org/licenses/>.
47 ###
48 ### In particular, no exception to the GPL is granted regarding generated
49 ### `configure' scripts which are the output of Autoconf.
50
51 AC_DEFUN([FINALLY_GCC_NESTED_FUNCTIONS_TEST_PROGRAM], [AC_LANG_PROGRAM([], [
52 __extension__ __inline__ void nested(void) { ; }
53 nested();
54 ])])
55 AC_DEFUN([FINALLY_GCC_ATTRIBUTE_CLEANUP_TEST_PROGRAM], [AC_LANG_PROGRAM([
56 extern void cleanup_fn(const int *x);
57 extern void bamboozle(int *x_inout);
58 ], [
59 __attribute__((cleanup(cleanup_fn))) int x = 0;
60 bamboozle(&x);
61 ])])
62 AC_DEFUN([FINALLY_GCC_REALLY_CLANG_TEST_PROGRAM], [AC_LANG_PROGRAM([], [
63 #ifndef __clang__
64 choke me
65 #endif
66 ])])
67 AC_DEFUN([FINALLY_CLANG_BLOCKS_TEST_PROGRAM], [AC_LANG_PROGRAM([
68 extern void srand(unsigned); /* might throw? */
69 static __inline__ void runblk(void (^*f)(void)) { (*f)(); }
70 ], [
71 unsigned x = 1; /* closed over */
72 __attribute__((__unused__, __cleanup__(runblk)))
73 void (^f)(void) = ^{ srand(x); };
74 srand(0);
75 ])])
76
77 dnl Decide whether we can define a plausible `FINALLY' macro.
78 AC_DEFUN([FINALLY_CHECK],
79 [finally_flavour=undecided finally_result="not supported"
80
81 dnl We're going to want to test C code.
82 AC_LANG_PUSH([C])
83
84 case $finally_flavour,$GCC in
85 undecided,yes)
86 dnl Our GCC-ish strategies have a common factor: they depend on
87 dnl `__attribute__((cleanup(...)))' working. So let's check for that.
88
89 AC_CACHE_CHECK([whether the alleged GNU C compiler supports \`__attribute__((cleanup(...)))'],
90 [finally_cv_gcc_attribute_cleanup_p], [
91 AC_COMPILE_IFELSE([FINALLY_GCC_ATTRIBUTE_CLEANUP_TEST_PROGRAM],
92 [finally_cv_gcc_attribute_cleanup_p=yes],
93 [finally_cv_gcc_attribute_cleanup_p=no])])
94 case $finally_cv_gcc_attribute_cleanup_p in
95 no) finally_flavour=NIL ;;
96 esac
97 ;;
98 esac
99
100 case $finally_flavour,$GCC in
101 undecided,yes)
102 dnl Autoconf has decided that the compiler smells a bit like GCC, and it
103 dnl certainly seems to support a GCC extension. But many compilers
104 dnl impersonate GCC, in more or less convincing ways. Our GCC-flavoured
105 dnl `FINALLY' code depends on nested functions, which GCC has supported
106 dnl pretty much forever, but other compilers don't even though they lie
107 dnl about being compatible.
108
109 AC_CACHE_CHECK([whether the alleged GNU C compiler supports nested functions],
110 [finally_cv_gcc_nested_functions_p], [
111 AC_COMPILE_IFELSE([FINALLY_GCC_NESTED_FUNCTIONS_TEST_PROGRAM],
112 [finally_cv_gcc_nested_functions_p=yes],
113 [finally_cv_gcc_nested_functions_p=no])])
114 case $finally_cv_gcc_nested_functions_p in
115 yes)
116 finally_flavour=GCC_NESTED_FUNCTIONS
117 finally_result="GCC nested functions"
118 ;;
119 esac
120 ;;
121 esac
122
123 case $finally_flavour,$GCC in
124 undecided,yes)
125 dnl That didn't work. I guess it's not really GCC after all. Maybe it's
126 dnl Clang wearing a false moustache.
127
128 AC_CACHE_CHECK([whether the impostor GNU C compiler is really Clang],
129 [finally_cv_gcc_really_clang_p], [
130 AC_COMPILE_IFELSE([FINALLY_GCC_REALLY_CLANG_TEST_PROGRAM],
131 [finally_cv_gcc_really_clang_p=yes],
132 [finally_cv_gcc_really_clang_p=no])])
133 finally_clang_p=$finally_cv_gcc_really_clang_p
134 ;;
135 *)
136 finally_clang_p=no
137 ;;
138 esac
139
140 case $finally_flavour,$finally_clang_p in
141 undecided,yes)
142 dnl Yup. Not a particularly convincing disguise, really. Well, at least
143 dnl Clang has a thing which looks a bit like nested functions, and a bit
144 dnl like closures, only with a terrible syntax, which it calls `blocks'.
145 dnl Unfortunately, we may or may not require varying levels of ceremony
146 dnl to make this work.
147
148 AC_CACHE_CHECK([which hacks are needed to persuade Clang to work with simple blocks],
149 [finally_cv_clang_blocks_hacks], [
150
151 dnl We'll need to mess with the compiler flags and libraries,
152 dnl so make sure we can put them back again afterwards.
153 finally_original_CFLAGS=$CFLAGS finally_original_LIBS=$LIBS
154
155 dnl Maintain a list of things that we did. This is the thing
156 dnl we'll cache.
157 unset hacks; win=t
158
159 case $win in
160 t)
161 dnl OK. First thing, we need to get the compiler proper to accept
162 dnl the `blocks' syntax. I guess the syntax is so hideous that
163 dnl Clang is sometimes ashamed to admit to parsing it unless we
164 dnl twist its arm. Apparently `-fblocks' is unnecessary on some
165 dnl targets, so let's see if we can manage without.
166
167 for pass in nil -fblocks; do
168 case $pass in -*) CFLAGS="$CFLAGS -fblocks" ;; esac
169 AC_COMPILE_IFELSE([FINALLY_CLANG_BLOCKS_TEST_PROGRAM],
170 [win=t], [win=nil])
171 case $win in t) break ;; esac
172 done
173 case $win,$pass in
174 *,nil | nil,*) ;;
175 *) hacks=${hacks+$hacks }$pass
176 esac
177 ;;
178 esac
179
180 case $win in
181 t)
182 dnl We got the compiler to accept the unpleasant syntax. The next
183 dnl problem is that, technically, the generated code depends on a
184 dnl runtime support library; only our use for these things is so
185 dnl simple that, at reasonable optimization settings, we can do
186 dnl without. So let's see if we're in that situation.
187
188 for pass in nil -lBlocksRuntime; do
189 case $pass in -l*) LIBS="$LIBS $pass" ;; esac
190 AC_LINK_IFELSE([FINALLY_CLANG_BLOCKS_TEST_PROGRAM],
191 [win=t], [win=nil])
192 case $win in t) break ;; esac
193 done
194 case $win,$pass in
195 *,nil | nil,*) ;;
196 *) hacks=${hacks+$hacks }$pass
197 esac
198 ;;
199 esac
200
201 dnl We've finished probing, and it's time to report our findings.
202 case $win in
203 t) finally_cv_clang_blocks_hacks=${hacks-none} ;;
204 *) finally_cv_clang_blocks_hacks=failed ;;
205 esac
206
207 dnl Oh! And don't forget to undo our fiddling with the compiler and
208 dnl linker settings.
209 CFLAGS=$finally_original_CFLAGS LIBS=$finally_original_LIBS])
210
211 dnl That was fun. Now we know how to persuade Clang to support these
212 dnl block thingummies (or not). Report our findings.
213 case $finally_cv_clang_blocks_hacks in
214 failed)
215 finally_flavour=NIL
216 ;;
217 *)
218 finally_flavour=CLANG_BLOCKS FINALLY_CFLAGS= FINALLY_LIBS=
219 finally_result="Clang blocks"
220 for hack in $finally_cv_clang_blocks_hacks; do
221 case $hack in
222 none) ;;
223 -l* | -L*) FINALLY_LIBS=${FINALLY_LIBS:+ $FINALLY_LIBS}$hack ;;
224 -*) FINALLY_CFLAGS=${FINALLY_CFLAGS:+ $FINALLY_CFLAGS}$hack ;;
225 *) AC_MSG_ERROR([confused by unexpected hack $hack]) ;;
226 esac
227 done
228 ;;
229 esac
230 ;;
231 esac
232
233 case $finally_flavour in
234 undecided)
235 dnl We've got this far and we've drawn a blank. Give up.
236 finally_flavour=NIL
237 ;;
238 esac
239
240 AC_LANG_POP([C])
241
242 dnl Pass the results on to the implementation machinery.
243 AC_MSG_CHECKING([how to implement deferred cleanup code])
244 AC_DEFINE_UNQUOTED([FINALLY_CONFIG_FLAVOUR],
245 [$finally_flavour],
246 [Select one of the implementation strategies for the `FINALLY' macro.])
247 AC_SUBST(FINALLY_CFLAGS) AC_SUBST(FINALLY_LIBS)
248 AC_MSG_RESULT([$finally_result])
249
250 dnl Invoke the caller's shell fragments according to our findings.
251 case $finally_flavour in
252 nil)
253 $2
254 ;;
255 *)
256 $1
257 ;;
258 esac
259 ])