X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/mLib/blobdiff_plain/3832000d8a0d417dbfda0ca2d26bc7d66f7f4741..08bb7015a9e28c5c9d38fe05a6f6644bc21fa527:/utils/macros.h diff --git a/utils/macros.h b/utils/macros.h index b960475..2ca5a06 100644 --- a/utils/macros.h +++ b/utils/macros.h @@ -32,134 +32,455 @@ extern "C" { #endif +/*----- Header files ------------------------------------------------------*/ + +#include + +#ifndef MLIB_COMPILER_H +# include "compiler.h" +#endif + /*----- Miscellaneous utility macros --------------------------------------*/ -#define N(v) (sizeof(v)/sizeof(*v)) +/* --- @N@ --- * + * + * Arguments: @type v[]@ = an actual array, not a pointer + * + * Returns: The number of elements in @v@. + */ + +#define N(v) (sizeof(v)/sizeof(*(v))) + +/* --- @STR@ --- * + * + * Arguments: @x@ = some tokens + * + * Returns: A string literal containing the macro-expanded text of @x@. + */ #define MLIB__STR(x) #x #define STR(x) MLIB__STR(x) +/* --- @GLUE@, @GLUE3@ --- * + * + * Arguments: @x, y@ = two sequences of tokens + * @z@ = a third sequence of tokens + * + * Returns: A single token formed by gluing together the macro-expansions + * of @x@ and @y@, and @z@ for @GLUE3@. + */ + #define MLIB__GLUE(x, y) x##y #define GLUE(x, y) MLIB__GLUE(x, y) +#define GLUE3(x, y, z) GLUE(x, MLIB__GLUE(y, z)) + +/* --- @STATIC_ASSERT@ --- * + * + * Arguments: @int cond@ = a condition + * @msg@ = a string literal message + * + * Returns: --- + * + * Use: Fail at compile time unless @cond@ is nonzero. The failure + * might report @msg@. + */ + +#ifdef static_assert +# define STATIC_ASSERT(cond, msg) static_assert(!!(cond), msg) +#else +# define STATIC_ASSERT(cond, msg) \ + IGNORABLE extern char static_assert_failed[1 - 2*!(cond)] +#endif -/*----- Compiler diagnostics ----------------------------------------------*/ - -/* --- Compiler-specific definitions --- */ - -#if defined(__GNUC__) - -# define GCC_VERSION_P(maj, min) \ - (__GNUC__ > (maj) || (__GNUC__ == (maj) && __GNUC_MINOR__ >= (min))) - -# if GCC_VERSION_P(2, 5) -# define NORETURN __attribute__((noreturn)) -# define PRINTF_LIKE(fix, aix) __attribute__((format(printf, fix, aix))) -# define SCANF_LIKE(fix, aix) __attribute__((format(scanf, fix, aix))) -# define IGNORABLE __attribute__((unused)) -# endif - -# if GCC_VERSION_P(4, 5) -# define DEPRECATED(msg) __attribute__((deprecated(msg))) -# elif GCC_VERSION_P(3, 1) -# define DEPRECATED(msg) __attribute__((deprecated)) -# endif - -# if GCC_VERSION_P(4, 0) -# define EXECL_LIKE(ntrail) __attribute__((sentinel(ntrail))) -# endif - -# if GCC_VERSION_P(4, 6) - - /* --- Diagnostic suppression in GCC: a tale of woe --- * - * - * This is extremely unpleasant, largely as a result of bugs in the GCC - * preprocessor's handling of @_Pragma@. The fundamental problem is - * that it's the preprocessor, and not the compiler proper, which - * detects @_Pragma@, emitting @#pragma@ lines into its output; and it - * does it during macro expansion, even if the macro is being expanded - * during argument collection. Since arguments are expanded before - * replacing the macro's invocation with its body, a pragma in an - * argument will be emitted %%\emph{before}%% any pragmata in the body, - * even if they appear before the argument in the body -- and even if - * the argument doesn't actually appear anywhere at all in the body. - * - * Another, rather less significant, problem is that @_Pragma@'s - * argument is a single string literal, recognized in translation phase - * 4, before string-literal concatenation in phase 6, so we must build - * pragma bodies as token lists and then stringify them. - * - * As a result, we need some subterfuge here. The @MLIB__PRAGMA_HACK@ - * macro issues a @_Pragma@ on its argument token list, which it - * stringifies; this deals with the second problem. The first is - * trickier: we must delay expansion of @MLIB__PRAGMA_HACK@ from the - * argument collection phase to the body rescanning phase, and we do - * this by splitting the invocations between @GCC_WARNING@ macro calls: - * the name is left hanging from the previous call (or from - * @MLIB__MUFFLE_WARNINGS@, in the first case) and the body is supplied - * by @GCC_WARNING@, which also supplies the next @MLIB__PRAGMA_HACK@. - * The remaining problem is to make sure we can dispose of the final - * trailing @MLIB__PRAGMA_HACK@ harmlessly, which we do by introducing - * an extra argument @emitp@, which may be either @t@ or @nil@; this - * dispatches to an appropriate helper macro by means of token-pasting. - * - * I'm so sorry. - */ - -# define MLIB__PRAGMA_HACK_t(x) _Pragma(#x) -# define MLIB__PRAGMA_HACK_nil(x) -# define MLIB__PRAGMA_HACK(emitp, x) MLIB__PRAGMA_HACK_##emitp(x) -# define MLIB__MUFFLE_WARNINGS(warns, body) \ +/* --- @CHECK_TYPE@ --- + * + * Arguments: @expty@ = expected type of @x@ + * @expty x@ = some object + * + * Returns: Integer zero. + * + * Use: Cause a compile-time failure unless the type of @x@ is + * assignment-compatible with @expty@. + */ + +#define CHECK_TYPE(expty, x) (!sizeof(*(expty *)0 = (x))) + +/* --- @CONVERT_CAREFULLY@ --- + * + * Arguments: @newty@ = new type for the result + * @expty@ = expected type of @x@ + * @expty x@ = some object + * + * Returns: @x@, but coerced to type @newty@. + * + * Use: Like @(newty)x@, except that it checks at compile-time that + * @x@ is at least assignment-compatible with type @expty@ + * before trying. + */ + +#define CONVERT_CAREFULLY(newty, expty, x) \ + (CHECK_TYPE(expty, x) + (/*unconst unvolatile*/ newty)(x)) + +/* --- @UNCONST@, @UNVOLATILE@, @UNQUALIFY@ --- * + * + * Arguments: @type@ = a type name + * @type *p@ = a pointer + * + * Returns: @p@, but without @const@, @volatile@ or both qualifiers. + * + * Use: Strips qualifiers from pointer types. + * + * The @UNCONST@ macro strips @const@. It checks that @p@ + * has type `pointer to @type@ or @const type@'; if not, a + * compile-time error results. Otherwise, it returns the value + * of @p@, converted to `pointer to (non-constant) @type@'. It + * will not silently strip a @volatile@ qualifier. + * + * The @UNVOLATILE@ macro is similar, except that it strips + * @volatile@ instead of @const@. The @UNQUALIFY@ macro strips + * both qualifiers. + */ + +#define UNCONST(type, p) CONVERT_CAREFULLY(type *, const type *, p) +#define UNVOLATILE(type, p) CONVERT_CAREFULLY(type *, volatile type *, p) +#define UNQUALIFY(type, p) \ + CONVERT_CAREFULLY(type *, const volatile type *, p) + +/* --- @EMPTY@ --- * + * + * Arguments: --- + * + * Returns: The empty token sequence. + */ + +#define EMPTY + +/* --- @COMMA@ --- * + * + * Arguments: --- + * + * Returns: A `%|,|%' token, which can be usefully passed to macros to + * avoid argument splitting. + */ + +#define COMMA , + +/*----- String and character hacks ----------------------------------------*/ + +/* --- @IS...@ --- * + * + * Arguments: @int ch@ = a character code, but not @EOF@ + * + * Returns: Nonzero if @ch@ is in the relevant @@ category. + * + * Use: Classifies characters, but safely even if characters are + * signed. + * + * There is a macro for each of the @@ @is...@ + * functions. + */ + +#define CTYPE_HACK(func, ch) (func((unsigned char)(ch))) + +#define ISALNUM(ch) CTYPE_HACK(isalnum, ch) +#define ISALPHA(ch) CTYPE_HACK(isalpha, ch) +#define ISASCII(ch) CTYPE_HACK(isascii, ch) +#define ISBLANK(ch) CTYPE_HACK(isblank, ch) +#define ISCNTRL(ch) CTYPE_HACK(iscntrl, ch) +#define ISDIGIT(ch) CTYPE_HACK(isdigit, ch) +#define ISGRAPH(ch) CTYPE_HACK(isgraph, ch) +#define ISLOWER(ch) CTYPE_HACK(islower, ch) +#define ISPRINT(ch) CTYPE_HACK(isprint, ch) +#define ISPUNCT(ch) CTYPE_HACK(ispunct, ch) +#define ISSPACE(ch) CTYPE_HACK(isspace, ch) +#define ISUPPER(ch) CTYPE_HACK(isupper, ch) +#define ISXDIGIT(ch) CTYPE_HACK(isxdigit, ch) + +/* --- @TO...@ --- * + * + * Arguments: @int ch@ = a character code, but not @EOF@ + * + * Returns: The converted character code. + * + * Use: Converts characters, but safely even if characters are + * signed. + * + * There is a macro for each of the @@ @to...@ + * functions. + */ + +#define TOASCII(ch) CTYPE_HACK(toascii, ch) +#define TOLOWER(ch) CTYPE_HACK(tolower, ch) +#define TOUPPER(ch) CTYPE_HACK(toupper, ch) + +/* --- @MEMCMP@, @STRCMP@, @STRNCMP@ --- * + * + * Arguments: @const type *x, *y@ = pointers to strings + * @op@ = a relational operator symbol + * @size_t n@ = length of the strings + * + * Returns: Nonzero if the relationship between the strings satisfies the + * operator @op@, otherwise zero. + * + * Use: These macros mitigate the author's frequent error of failing + * to compare the result of the underlying standard functions + * against zero, effectively reversing the sense of an intended + * test for equality. + */ + +#define MEMCMP(x, op, y, n) (memcmp((x), (y), (n)) op 0) +#define STRCMP(x, op, y) (strcmp((x), (y)) op 0) +#define STRNCMP(x, op, y, n) (strncmp((x), (y), (n)) op 0) + +/*----- Compiler-specific definitions -------------------------------------*/ + +/* The descriptions of these are given below, with the fallback + * definitions. + */ + +#if GCC_VERSION_P(2, 5) || CLANG_VERSION_P(3, 3) +# define NORETURN __attribute__((__noreturn__)) +# define PRINTF_LIKE(fix, aix) __attribute__((__format__(printf, fix, aix))) +# define SCANF_LIKE(fix, aix) __attribute__((__format__(scanf, fix, aix))) +# define IGNORABLE __attribute__((__unused__)) +#endif + +#if GCC_VERSION_P(3, 4) || CLANG_VERSION_P(3, 3) +# define MUST_CHECK __attribute__((__warn_unused_result__)) +#endif + +#if GCC_VERSION_P(4, 5) || CLANG_VERSION_P(3, 3) +# define DEPRECATED(msg) __attribute__((__deprecated__(msg))) +#elif GCC_VERSION_P(3, 1) +# define DEPRECATED(msg) __attribute__((__deprecated__)) +#endif + +#if GCC_VERSION_P(4, 0) || CLANG_VERSION_P(3, 3) +# define EXECL_LIKE(ntrail) __attribute__((__sentinel__(ntrail))) +#endif + +#if GCC_VERSION_P(2, 7) || CLANG_VERSION_P(0, 0) +# define LAUNDER(x) \ + ({ __typeof__(x) _y; __asm__("" : "=g"(_y) : "0"(x)); _y; }) +# define ADMIRE(x) \ + ({ __asm__("" :: "g"(x)); }) +# define ADMIRE_BUF(p, sz) \ + ({ __asm__("" :: "m"(*(unsigned char *)p), "g"(sz) : "memory"); }) +# define RELAX do __asm__(""); while (0) +#endif + +#if CLANG_VERSION_P(3, 3) + +# define MLIB__PRAGMA_HACK(x) _Pragma(#x) +# define MLIB__MUFFLE_WARNINGS(warns, body) \ + _Pragma("clang diagnostic push") \ + warns \ + body \ + _Pragma("clang diagnostic pop") +# define CLANG_WARNING(warn) \ + MLIB__PRAGMA_HACK(clang diagnostic ignored warn) +# define MUFFLE_WARNINGS_DECL(warns, body) \ + MLIB__MUFFLE_WARNINGS(warns, body) +# define MUFFLE_WARNINGS_EXPR(warns, body) \ + __extension__ ({ MLIB__MUFFLE_WARNINGS(warns, (body);) }) +# define MUFFLE_WARNINGS_STMT(warns, body) \ + do { MLIB__MUFFLE_WARNINGS(warns, body) } while (0) + +#endif + +#if GCC_VERSION_P(4, 6) + + /* --- Diagnostic suppression in GCC: a tale of woe --- * + * + * This is extremely unpleasant, largely as a result of bugs in the GCC + * preprocessor's handling of @_Pragma@. The fundamental problem is + * that it's the preprocessor, and not the compiler proper, which + * detects @_Pragma@, emitting @#pragma@ lines into its output; and it + * does it during macro expansion, even if the macro is being expanded + * during argument collection. Since arguments are expanded before + * replacing the macro's invocation with its body, a pragma in an + * argument will be emitted %%\emph{before}%% any pragmata in the body, + * even if they appear before the argument in the body -- and even if + * the argument doesn't actually appear anywhere at all in the body. + * + * Another, rather less significant, problem is that @_Pragma@'s + * argument is a single string literal, recognized in translation phase + * 4, before string-literal concatenation in phase 6, so we must build + * pragma bodies as token lists and then stringify them. + * + * As a result, we need some subterfuge here. The @MLIB__PRAGMA_HACK@ + * macro issues a @_Pragma@ on its argument token list, which it + * stringifies; this deals with the second problem. The first is + * trickier: we must delay expansion of @MLIB__PRAGMA_HACK@ from the + * argument collection phase to the body rescanning phase, and we do + * this by splitting the invocations between @GCC_WARNING@ macro calls: + * the name is left hanging from the previous call (or from + * @MLIB__MUFFLE_WARNINGS@, in the first case) and the body is supplied + * by @GCC_WARNING@, which also supplies the next @MLIB__PRAGMA_HACK@. + * The remaining problem is to make sure we can dispose of the final + * trailing @MLIB__PRAGMA_HACK@ harmlessly, which we do by introducing + * an extra argument @emitp@, which may be either @t@ or @nil@; this + * dispatches to an appropriate helper macro by means of token-pasting. + * + * I'm so sorry. + */ + +# define MLIB__PRAGMA_HACK_t(x) _Pragma(#x) +# define MLIB__PRAGMA_HACK_nil(x) +# define MLIB__PRAGMA_HACK(emitp, x) MLIB__PRAGMA_HACK_##emitp(x) +# define MLIB__MUFFLE_WARNINGS(warns, body) \ _Pragma("GCC diagnostic push") MLIB__PRAGMA_HACK \ warns \ (nil, nil) \ body \ _Pragma("GCC diagnostic pop") -# define GCC_WARNING(warn) \ +# define GCC_WARNING(warn) \ (t, GCC diagnostic ignored warn) MLIB__PRAGMA_HACK -# define MUFFLE_WARNINGS_DECL(warns, body) \ +# define MUFFLE_WARNINGS_DECL(warns, body) \ MLIB__MUFFLE_WARNINGS(warns, body) -# define MUFFLE_WARNINGS_EXPR(warns, body) \ +# define MUFFLE_WARNINGS_EXPR(warns, body) \ __extension__ ({ MLIB__MUFFLE_WARNINGS(warns, (body);) }) -# define MUFFLE_WARNINGS_STMT(warns, body) \ +# define MUFFLE_WARNINGS_STMT(warns, body) \ do { MLIB__MUFFLE_WARNINGS(warns, body) } while (0) -# endif - #endif /* --- Fallback definitions, mostly trivial --- */ -#ifndef GCC_VERSION_P -# define GCC_VERSION_P(maj, min) 0 +/* --- @DISCARD@ --- * + * + * Arguments: @x@ = a function call + * + * Returns: --- + * + * Use: Explicitly discard the result of @x@. This counteracts a + * @MUST_CHECK@ attribute on the called function. + */ + +#ifndef DISCARD +# define DISCARD(x) do if (x); while (0) #endif -#ifndef DEPRECATED -# define DEPRECATED(msg) +/* --- @IGNORE@ --- * + * + * Arguments: @x@ = any expression + * + * Returns: --- + * + * Use: Ignore the value of @x@, overriding compiler warnings. + */ + +#ifndef IGNORE +# define IGNORE(x) ((void)(x)) #endif -#ifndef EXECL_LIKE -# define EXECL_LIKE(ntrail) +/* --- @LAUNDER@ --- * + * + * Arguments: @x@ = some integer expression + * + * Returns: @x@. + * + * Use: Causes a compiler to know nothing about the value of @x@, + * even if it looks obvious, e.g., it's a constant. + */ + +#ifndef LAUNDER +# define LAUNDER(x) (x) #endif -#ifndef DISCARD -# define DISCARD(x) do if (x); while (0) +/* --- @ADMIRE@, @ADMIRE_BUF@ --- * + * + * Arguments: @x@ = some scalar expression + * @const void *p@, @size_t sz@ = a pointer and length + * + * Returns: --- + * + * Use: Ensures that the compiler generates code to compute @x@ or + * the contents of the buffer at @p@. + */ + +#ifndef ADMIRE +# define ADMIRE(x) ((void)(x)) +#endif +#ifndef ADMIRE_BUF +# define ADMIRE_BUF(p, sz) ((void)(p), (void)(sz)) #endif -#ifndef IGNORE -# define IGNORE(x) ((void)(x)) +/* --- @RELAX@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Does nothing, but the compiler doesn't know that. + */ + +#ifndef RELAX +# define RELAX #endif -#ifndef MUFFLE_WARNINGS_DECL -# define MUFFLE_WARNINGS_DECL(warns, body) body +/* --- @DEPRECATED@, @NORETURN@, @IGNORABLE@, @MUST_CHECK@ --- * + * + * Use: These are (mostly) function attributes; write them among the + * declaration specifiers for a function definition or + * declaration. These may not do anything, but the intended + * behaviour is as follows. + * + * * @DEPRECATED(msg)@ -- report a warning, quoting the string + * literal @msg@, if the function is called. + * + * * @NORETURN@ -- promise that the function doesn't return to + * its caller: either it kills the process, or it performs + * some nonlocal transfer. + * + * * @IGNORABLE@ -- the item (which might be data rather than + * a function) might not be referred to, but that's OK: + * don't warn about it. + * + * @ @MUST_CHECK@ -- warn if the return value of a function is + * ignored. Use @DISCARD@ if you really don't care. + */ + +#ifndef DEPRECATED +# define DEPRECATED(msg) #endif -#ifndef MUFFLE_WARNINGS_EXPR -# define MUFFLE_WARNINGS_EXPR(warns, body) (body) +#ifndef NORETURN +# define NORETURN #endif -#ifndef MUFFLE_WARNINGS_STMT -# define MUFFLE_WARNINGS_STMT(warns, body) do { body } while (0) +#ifndef IGNORABLE +# define IGNORABLE #endif +#ifndef MUST_CHECK +# define MUST_CHECK +#endif + +/* --- @PRINTF_LIKE@, @SCANF_LIKE@, @EXECL_LIKE@ --- * + * + * Arguments: @int fmtix@ = format string argument index (starting from 1) + * @int argix@ = variable format argument tail index (starting + * from 1) + * @int ntrail@ = number of arguments following terminator + * + * Use: These are function attributes. Again, they might not do + * anything at all. By intention, they give the compiler + * information about a variadic function's arguments, so that it + * can warn about misuse. + * + * * @PRINTF_LIKE@ -- the function takes a @printf@-style + * format string as argument @fmtix@ and an argument tail + * (which may be empty) beginning with argument @argix@. + * + * * @SCANF_LIKE@ -- the function takes a @scanf@-style + * format string as argument @fmtix@ and an argument tail + * (which may be empty) beginning with argument @argix@. + * + * * @EXECL_LIKE@ -- the function takes a sequence of pointer + * arguments terminated by a null pointer, followed by + * @ntrail@ further arguments. + */ + #ifndef PRINTF_LIKE # define PRINF_LIKE(fmtix, argix) #endif @@ -168,14 +489,65 @@ # define SCANF_LIKE(fmtix, argix) #endif -#ifndef IGNORABLE -# define IGNORABLE +#ifndef EXECL_LIKE +# define EXECL_LIKE(ntrail) +#endif + +/* --- @MUFFLE_WARNINGS_...@ --- * + * + * Arguments: @warns@ = a sequence of @..._WARNING@ calls (see below) + * @body@ = some program text + * + * Use: Muffle specific warnings within the program text. + * + * For @MUFFLE_WARNINGS_DECL@, the program text is a + * declaration; for @MUFFLE_WARNINGS_EXPR@, it is an expression, + * and for @MUFFLE_WARNINGS_STMT@, it is a statement. + * + * The warnings to be muffled are given as a list of + * @..._WARNING@ macros, with no separators. The list can + * list warnings from multiple different compilers: entries for + * irrelevant compilers will be ignored. + */ + +#ifndef MUFFLE_WARNINGS_DECL +# define MUFFLE_WARNINGS_DECL(warns, body) body +#endif + +#ifndef MUFFLE_WARNINGS_EXPR +# define MUFFLE_WARNINGS_EXPR(warns, body) (body) +#endif + +#ifndef MUFFLE_WARNINGS_STMT +# define MUFFLE_WARNINGS_STMT(warns, body) do { body } while (0) #endif +/* --- @GCC_WARNING@ --- * + * + * Arguments: @warn@ = a string literal naming a warning, with `%|-W...|%' + * prefix + * + * Use: Names a GCC warning: use within @MUFFLE_WARNINGS_...@. + * + * Note that GCC's warning suppression is very buggy. + */ + #ifndef GCC_WARNING # define GCC_WARNING(warn) #endif +/* --- @CLANG_WARNING@ --- * + * + * Arguments: @warn@ = a string literal naming a warning, with `%|-W...|%' + * prefix + * + * Use: Names a Clang warning: use within @MUFFLE_WARNINGS_...@. + */ + +#ifndef CLANG_WARNING +# define CLANG_WARNING(warn) +#endif + /*----- That's all, folks -------------------------------------------------*/ #ifdef __cplusplus