EEVblog Electronics Community Forum

Products => Computers => Programming => Topic started by: Simon on July 25, 2023, 09:43:57 am

Title: Generating custom compile errors in GCC C
Post by: Simon on July 25, 2023, 09:43:57 am
I'm writing setup code and from experience I can be prone to doing things like allocating the same pin or counter twice. To stop this I have written code that will track what resources have been used and send the program into a while loop where I will find it with a debugger. But I would rather catch these issues at compile time. Is there a way to generate custom compile errors in GCC ?

I know that I can do this with the pre-processor but I want to avoid lots of macros.
Title: Re: Generating custom compile errors in GCC C
Post by: mfro on July 25, 2023, 10:11:49 am
... Is there a way to generate custom compile errors in GCC ? ...

in C11, there is static_assert() (or _Static_assert(), respectively). For anything earlier, you'll be bound to the preprocessor.
Title: Re: Generating custom compile errors in GCC C
Post by: DiTBho on July 25, 2023, 11:39:15 am
allocating the same pin or counter twice

how do you allocate pins?
Title: Re: Generating custom compile errors in GCC C
Post by: ejeffrey on July 25, 2023, 11:52:56 am
You can use #error from the preprocessor for instance but the hard part is doing to be actually detecting the problem at compile time which depends on exactly what a bug looks like.

Are you looking for something like calling the same function with the same constant argument from two call sites regardless of control flow?  You might just be able to detect this with grep and a custom build check and avoid the compiler altogether.
Title: Re: Generating custom compile errors in GCC C
Post by: Psi on July 25, 2023, 11:57:14 am
You can use #warning and #error
They will appear in your compile messages and you can add a text string after that will show up as well.
Error will cause the compile to fail. Warning will just show a message as a warning but still compile.

You can use them to make notes as you code that will show up later when you compile.
eg

#warning I should get around to improving this function some time
or
#error Reminder, this block of code must be fixed before it's going to work

Or you can combine with #if or #ifdef to create some checks that happen every time you compile
to ensure your defines are the way you expected.
eg

#define SettingNum 123


#if (defined SettingNum ) && (SettingNum > 255)
    #error The SettingNum feature was only written to support bytes, max 255
#endif

#if  ( ! defined SettingNum )
    #warning SettingNum is not defined, did you mean to do that
#endif

#if  (defined SettingNum ) && (SettingNum < 0)
   #error WTF are you doing making SettingNum negative, PEBKAC
#endif
Title: Re: Generating custom compile errors in GCC C
Post by: Simon on July 25, 2023, 02:43:15 pm
allocating the same pin or counter twice

how do you allocate pins?

Code: [Select]

static uint32_t port_pin_in_use[port] ;

void allocate_port_pin(uint8_t port_pin)
{
uint8_t port = port_pin >> 5 ;
uint8_t  pin = port & 0b11111 ;

if (port_pin_in_use[port] & 0x1 << pin)
{
while (1)
{
; // this is the second time you are using this pin
}
}

else port_pin_in_use[port] |= 0x1 << pin ;
}


The port pin argument is simply an enumeration of PA00, PA01.......PB00, PB01.... to numbers starting at 0
Title: Re: Generating custom compile errors in GCC C
Post by: Simon on July 25, 2023, 02:58:40 pm
_Static_assert(0,"test") ; Works indeed. Perfect. What confuses me is why have the changed the name between C11 and C23? I know, I can define one as the other...... Maybe I need a pre-processor flag, you never escape the pre-processor...... :)
Title: Re: Generating custom compile errors in GCC C
Post by: Psi on July 25, 2023, 10:43:28 pm
If you move all your pin/port defines into their own hardware port config file its easier to manage and see that there are no duplicates.

eg
#define LED_PIN                      PA00
#define LED_PIN_BV    _BV(LED_PIN)
You can also have a macro #define to init the data direction and setup the pin.
#define INIT_LED    do {   DDRA |= LED_PIN_BV; PORTA &= ~LED_PIN_BV;    } while (0)


And when you make a new revision of the PCB that has moved IO around a little all you need to do is sub-out for a different config file. Which can be done with a simple define for the board version.
Title: Re: Generating custom compile errors in GCC C
Post by: ejeffrey on July 26, 2023, 01:18:50 am
_Static_assert(0,"test") ; Works indeed. Perfect. What confuses me is why have the changed the name between C11 and C23? I know, I can define one as the other...... Maybe I need a pre-processor flag, you never escape the pre-processor...... :)

It's for backwards compatibility. In c11 _Static_assert was the keyword, but if you include assert.h you get static_assert as a defined macro.  This avoids clobbering existing code that used identifiers with that name.  After 10 years to allow migration the requirement for the header was removed.
Title: Re: Generating custom compile errors in GCC C
Post by: Simon on July 26, 2023, 09:58:11 am
I see, I guess the microchip compiler is behind then and must be C11 even though up to date.
Title: Re: Generating custom compile errors in GCC C
Post by: Simon on July 26, 2023, 01:45:51 pm
Oh well, that was short lived, nothing works, it just keeps making stupid demands, apparently the value tested has to be a constant. Very useful, not assert.h no longer has it.
Title: Re: Generating custom compile errors in GCC C
Post by: ejeffrey on July 26, 2023, 04:09:42 pm
Yeah it's not possible to tell at compile time how many times a given function is called.  static_assert only works expressions that can be evaluated at compile time. I'm not sure how you expected that to work.

Code: [Select]
grep -c 'allocate_port_pin\(PA00\)' *.c

 and check for greater than 1 is probably about the best you can do. But it for sure can have false negatives and positives.
Title: Re: Generating custom compile errors in GCC C
Post by: TheCalligrapher on July 26, 2023, 04:43:27 pm
_Static_assert(0,"test") ; Works indeed. Perfect. What confuses me is why have the changed the name between C11 and C23?

Since the very beginning (C99) it was `_Static_assert`, plus a `static_assert` macro defined in <assert.h>. In C23 `static_assert` became a built-in language feature, while `_Static_assert` became deprecated (yet still supported). So, there should really be no observable "chahge of name" in real-life code.
Title: Re: Generating custom compile errors in GCC C
Post by: Nominal Animal on July 26, 2023, 08:05:16 pm
Here is a no-runtime-overhead approach that works:

Use a specific section for the used pin numbers, and a preprocessor macro to declare the pin allocated:
Code: [Select]
#define  PIN_SECTION        "pins"
#define  MERGE4_(a,b,c,d)   a ## b ## c ## d
#define  MERGE4(a,b,c,d)    MERGE4_(a,b,c,d)
#define  NAMELINE(prefix)   MERGE4(__, prefix, _, __LINE__)
#define  ALLOCATE_PIN(pin, message)  \
    static const char NAMELINE(pin_alloc) \
    __attribute__((section (PIN_SECTION), used)) = pin

int main(void)
{
    ALLOCATE_PIN(1, "First example");
    ALLOCATE_PIN(2, "Second example");
}

ALLOCATE_PIN(3, "Third example");
The message is not recorded, it is only for the human programmers.  An allocation can occur in any scope, even inside functions, even inside a loop iteration.

Then, in your build, when all .o files have been compiled, you collect their pin allocations, and check if any pin is allocated more than once anywhere.  Using Bash, od, tr, awk, and objcopy (included as part of the target binutils toolchain):
Code: [Select]
for file in *.o ; do objcopy -O binary -j pins "$file" /dev/stdout ; done | od -A none -t d1 | tr '\t\n\v\f\r ' '\n\n\n\n\n\n' | awk 'BEGIN { split("", pin) } NF==1 { pin[$1]++ } END { status=0; for (p in pin) if (pin[p] > 1) { status=1 ; printf "Pin %d (0x%02x) used %d times!\n", p, pin[p], pin[p] } exit(status) }'

(This works on all targets and toolchains that use ELF object files, it is not Linux/BSD specific at all; I just used the tools I currently have for the latter example, because it was the quickest way.)

As is, the above is just the proof of concept, allowing the macro use anywhere, but not recording the message.  There is no runtime overhead at all, as long as the "pins" section is discarded from the final binary.  The same approach can be used for resource allocation, even external memory ranges in EEPROMs and PSRAMs not mapped to the device address space, of course, just using a fixed-size structure instead of a single unsigned char.

With a bit more complex example, using a fixed-size structure to include the pin, the pin name stringified, the file name (__FILE__ macro), the line number (__LINE__), and the message, a slightly more complex check can describe the exact conflicting allocations, with absolutely no runtime overhead.  The only overhead is increased build-time object file size, as they grow larger by the structures, but the final binary is not affected at all.  I can write such a checker (reading objcopy output, or internally invoking objcopy) in C or Python 3 for example, but note that this would be run on the development machine at build time, not on the target device.  Python 3 might be more portable, as I prefer C99 + POSIX interfaces.  (I wanted to try keep this post short, you see. :-[)

If you want to actually use the latter, I can create a larger example, and a suitable object file checker for you.  It is not complicated at all, as you can put everything behind a single header file, and if you run the checker as part of the build process using make or cmake or derivatives, they will abort the build if the checker returns nonzero.
Title: Re: Generating custom compile errors in GCC C
Post by: Simon on July 27, 2023, 06:40:45 am
Let me guess, this is something that C++ just does.
Title: Re: Generating custom compile errors in GCC C
Post by: DiTBho on July 27, 2023, 08:38:50 am
Code: [Select]
#define  ALLOCATE_PIN(pin, message)  \
    static const char NAMELINE(pin_alloc) \
    __attribute__((section (PIN_SECTION), used)) = pin

Brilliant approach!
You take advantage of the sections, which due to how the linker script is made can be "skimmed" (filtered) in the final executable.
Title: Re: Generating custom compile errors in GCC C
Post by: Nominal Animal on July 27, 2023, 02:35:52 pm
Let me guess, this is something that C++ just does.
I haven't seen anything like the no-runtime overhead ELF-section-based allocation I showed above, done in C++.
Of course, the same works just fine in C++ as well (and even as-is, within extern C { ... }).

It is really not C or C-variant specific, as it only requires that the build toolchain uses ELF format object files, and that the C compiler supports the section attribute (which all C compilers producing ELF object files, including gcc, clang, Intel Compiler Collection, etc. that I have ever managed to test, do support).

You take advantage of the sections, which due to how the linker script is made can be "skimmed" (filtered) in the final executable.
Exactly.  The easiest way to do this is to add
    /DISCARD/ { *(pins) }
at the beginning of the first SECTIONS block in your linker script.

I do like the ELF section approach for all kinds of build-time checking and information collation.

The same approach can be done for any kind of allocation, be they address ranges, interrupt numbers, or even strings (command and variable names used in a communication protocol with a host/device).  You can obtain the raw data from an .o file using objcopy -O binary -j pins file.o /dev/stdout (using the specific objcopy for the target device, part of the build toolchain), so the filtering/checking program just needs to parse the binary contents back to the structures used; noting that the format and types are based on the target device, so you cannot assume the development machine has the same conventions (byte order in particular).  The filtering/checking program can even sort and output derived data, say an array or index table for a menu (so you can create menu entries anywhere in code), and emit it as a C source file, or a binary data you insert into some object file with objcopy -I binary ...

Such compile-time allocators should not reference the address of a variable, though.  This information is lost in the pure binary dumps of the section, as relocation information is separate.

A few months ago I struggled with exactly this, as I have many use cases where specific ordering of the entries in the section would be really useful.  For example, consider a firmware that communicates using some command-response protocol, where each source file could define new commands.  If the command name and the handling function were recorded in such a structure, we could externally sort these into an optimum order; for example, into a perfect hash table with no collisions, or into alphabetic order so that binary search can be used.  Or just a hash table with a fixed hash function.  The order in which the linker combines the sections from different object files, is basically unpredictable (except with a specific version of a specific toolchain).

The solution I found was to export all the variables and functions that are referenced (taken the address of in such structures), and then simply record their global name as a string in the section.  Then, the filter/checker/sorter program just re-emits the section data as a C source file (that will be compiled last, only just before linking all together into the final binary), where the address reference is &name, with extern type name; declaration for each before the array construct.  type does differ for variables and functions, but in general, there is no need to be exactly correct with the type, as the type is not recorded in the ELF object file.  That is, you can use extern char name; for all variables, and a single function pointer type for all functions, because the name is all that is recorded in the ELF file for C code.

For perfect hashing, that C source file can also contain the perfect hashing algorithm adapted for that particular dataset, generated among candidate variable hash functions that you can brute-force check at filter/checker/sorter run time (build time for the target binary).

For menus, you can have the section structure describe the chain or always-present main entry off which it'll hang off from, and perhaps a priority within that list, with the C source file exporting a graph with two-way links between each menu entry (along each dimension the user can travel, so usually two prev-next pairs of pointers to menu entries).  That way, any place in your source files can export its controlling menu entry directly, with only the menu entry structure variable being exported (so no static in the macro shown in my previous message), and the checker/sorter program reconstructs the entire menu graph, perhaps even exporting it for documentation in Graphviz form.  All that data will reside in Flash, and only the current menu entry pointer will be in RAM.

I do love exploiting ELF sections to make my life as a software designer/engineer and developer easier.  It is a pity they're not more widely exploited; they're an awfully powerful tool for build-time shenanigans!  ;D
Title: Re: Generating custom compile errors in GCC C
Post by: westfw on August 01, 2023, 12:02:13 am
I have this interesting hack that I've used.  It puts functions that are defined with the "error" attribute in places where the compiler will eliminate them (due to optimization) when there is no problem.


Code: [Select]
boolean NonConstantUsed( void ) __attribute__ (( error("") ));
void LTO_Not_Enabled(void) __attribute__ (( error("") ));


#define digitalWriteFast(pin, val)                                      \
    if (__builtin_constant_p(pin)) {_dwfast(pin, val);} else {NonConstantUsed();}


static inline __attribute__((always_inline)) void _dwfast(int pin, int val) {
    uint8_t mask = digital_pin_to_bit_mask_PGM[pin];
    uint8_t port = digital_pin_to_port_PGM[pin];
    volatile uint8_t *outport = (volatile uint8_t*)port_to_output_PGM[port];
    if (digital_pin_to_bit_mask_PGM[0] == 0x99)  // Never true
        LTO_Not_Enabled();
    if (val) {
        *outport |= mask;
    } else {
        *outport &= ~mask;
    }
}


The if (digital_pin_to_bit_mask_PGM[0] == 0x99)  // Never true is especially interesting.  In the case where the array is available at compile time, the "if" can be evaluated at compile time as well, so there is no need to call the function.  If it's external, the compiler will try to insert the function call, and cause the error.
Title: Re: Generating custom compile errors in GCC C
Post by: 5U4GB on August 05, 2023, 09:15:48 am
... Is there a way to generate custom compile errors in GCC ? ...

in C11, there is static_assert() (or _Static_assert(), respectively). For anything earlier, you'll be bound to the preprocessor.

You can mostly emulate static_assert in pre-C11 using the preprocessor, if there's any interest in this let me know and I'll dig up the code.
Title: Re: Generating custom compile errors in GCC C
Post by: Simon on August 06, 2023, 03:14:50 pm
Well the preprocessor is easy as you use #warning and #error but I would be having to create a whole system of preprocessor code. This means that now I have to use the preprocessor to write a lot of the code, this makes it more complicated from what I have now. I will leave it alone. If anything one day I may use a serial port as standard to debug these things and output such messages rather than leave it stuck in the while loop as that can be made even more specific.
Title: Re: Generating custom compile errors in GCC C
Post by: SiliconWizard on August 06, 2023, 08:50:59 pm
static_assert is a great addition in C11 and later. Unless you are using an old compiler not supporting at least C11, there's little reason not to use it.
If you're using GCC for any target that is currently maintained, then C11 support has been there for over ten years.
(One reason you may not have access to C11 is if you use a proprietary compiler that doesn't support it, or if your current dev team's guidelines forces you to stick to C99 or even C89. If you're the decider, don't fret.)
Title: Re: Generating custom compile errors in GCC C
Post by: westfw on August 07, 2023, 05:05:55 am
Another possibility is to use inline asm.
asm (at least with the gnu assembler) has "deeper visibility" into things like existing symbols and their values.
For instance:
Code: [Select]

int main(void)
{
  asm(".ifdef main\n"
      ".warning \"main is already defined, from ASM\"\n"
      ".endif\n");
#ifdef main
#warning main is already defined, from C
#endif
}
Title: Re: Generating custom compile errors in GCC C
Post by: Nominal Animal on August 07, 2023, 09:59:45 am
The interesting ones the Linux kernel has historically used:

    #define BUILD_BUG_ON_ZERO(expr) (sizeof(struct { int:(-!!(expr)); }))
which causes a build-time failure if expr is true or nonzero.  This relies on C99.  When expr is zero or false, it calculates the size of a structure with an unnamed bitfield of zero width (which indicates no more bitfields in the current unit), so normally the expression evaluates to zero.  However, when expr is nonzero, it tries to calculate the size of a structure with an anonymous bitfield of size -1 bits.

The _ZERO suffix is not an error: it does not mean that this does a build bug when the expression is zero, it refers to the fact that normally, this expression returns zero.

    #define BUILD_BUG_ON(expr) ((void)sizeof(char[1 - 2*!!(expr)]))
also causes a build-time failure if expr is nonzero.  When expr is zero or false, it is simply an unused sizeof expression of a one-char array.  When expr is true or nonzero, it becomes an unused sizeof expression of an array with -1 chars, and thus should lead to a compile-time error.

    #define BUILD_BUG_ON_INVALID(expr) ((void)(sizeof((long)(expr))))
This verifies that expr is valid, without evaluating any side effects in expr.  (For example, BUILD_BUG_ON_INVALID(x++) checks that x is a valid variable, but does not increment it.)

In any case, I do recommend using static_assert(expr, message_string), with one of the header files containing
    #define static_assert(expr, msg)  _Static_assert(expr, msg)
and expand on that when you encounter compilers that do not support it.  Each compiler does provide identifiable pre-defined macros (https://sourceforge.net/p/predef/wiki/Home/) one can #ifdef about, and implement a suitable workaround.  Even sdcc has supported _Static_assert since version 3.3.0 (released in 2013).

If you want to future-proof that, use
Code: [Select]
#if !defined(static_assert)
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
#define static_assert(expr, msg) static_assert(expr, msg)
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
#define static_assert(expr, msg) _Static_assert(expr, msg)
#else
#define static_assert(expr, msg) ((void)sizeof(char[1 - 2*!(expr)]))
#endif
#endif /* !defined(static_assert) */
which uses the __STDC_VERSION__ pre-defined macro the compiler is supposed to set.  If it is already a macro, it does not redefine it.  If the compiler advertises itself as a C23 one, then static_assert is used.  If not, but the compiler advertises itself as a C11 one, _Static_assert is used.  Otherwise, it falls back to evaluating the size of an array of chars with one element if expr is true, and -1 elements if expr is false (losing the message, though).

That assumes C23 will have __STDC_VERSION__ == 202311L.