sdcc -v
SDCC : z80/sm83/mos6502/mos65c02 TD- 4.6.2 #16705 (Linux)
Note: this seems related to a previously resolved issue: https://sourceforge.net/p/sdcc/bugs/3153/
Duplicate symbols for inline functions in header files are being generated under specific circumstances with --debug enabled for sdcc. When this happens with multiple source files the symbols clash and cause the build to fail. Such as:
Multiple definition of C$someheader.h$81$2_0$286
Normally(??) with --debug when an inline function is used it will either not emit a symbol or will emit one using the name of the current source file, such as:
C$some_source_file.c$85$3_0$341 = .
.globl C$some_source_file.c$85$3_0$341
;some_source_file.c:85: inline_func(param, param);
The specific issue is non-globally unique symbols (and so potentially clashing) are (sometimes?) emitted when inline functions are used multiple times within a conditional statement. The emitted symbol for the resulting code uses the name of the header file and line number instead of the current source file. An example of such a symbol:
C$someheader.h$9$2_0$8 = .
.globl C$someheader.h$9$2_0$8
;someheader.h:9: p_struct->var2 = value;
In trying to reproduce the issue there was an additional condition that seemed required. It would only manifest with an extern pointer to a struct, but not references to a non-pointer extern struct. No idea if this is just a quirk of the particular test case or not.
would manifest:
extern some_struct_t * my_struct;
...
(conditional)
inline_func(my_struct, ...)
...
inline_func(my_struct, ...)
would not manifest:
extern some_struct_t my_struct;
...
(conditional)
inline_func(&my_struct, ...)
...
inline_func(&my_struct, ...)
Building with:
sdcc -msm83 --no-std-crt0 --fsigned-char --use-stdout --debug -Wa-pogN -c main.c -o main.o
Test case:
main.c
#include "someheader.h"
unsigned char conditional_var;
extern some_struct_t * my_struct;
void main(void) {
}
void THIS_IFELSE_WILL_EMIT_HEADER_SYMBOL_FOR_INLINE_FUNCS(void) {
if (conditional_var == 1) {
inline_func(my_struct, 1);
} else {
inline_func(my_struct, 0);
}
}
void THIS_CASE_WILL_ALSO_EMIT_HEADER_SYMBOL_FOR_INLINE_FUNCS(void) {
switch (conditional_var) {
case 1: inline_func(my_struct, 1); break;
default: inline_func(my_struct, 0); break;
}
}
void THIS_WILL___NOT___EMIT_HEADER_SYMBOL_FOR_INLINE_FUNCS(void) {
if (conditional_var == 1) {
inline_func(my_struct, 1);
}
inline_func(my_struct, 0);
}
someheader.h
typedef struct some_struct_t {
unsigned char var1;
unsigned char var2;
} some_struct_t;
inline void inline_func(some_struct_t * p_struct, unsigned char value) {
p_struct->var2 = value;
}
Resulting output (edited for brevity):
...
_THIS_IFELSE_WILL_EMIT_HEADER_SYMBOL_FOR_INLINE_FUNCS::
...
ld a, (_my_struct)
ld hl, #_my_struct + 1
ld h, (hl)
C$someheader.h$9$2_0$8 = .
.globl C$someheader.h$9$2_0$8 <------------------- May clash with other source files
;someheader.h:9: p_struct->var2 = value;
ld l, a
inc hl
...
dec a
pop hl
jr nz, 00102$
ld (hl), #0x01
ret
00102$:
ld (hl), #0x00
ret
_THIS_CASE_WILL_ALSO_EMIT_HEADER_SYMBOL_FOR_INLINE_FUNCS::
...
ld a, (_my_struct)
ld hl, #_my_struct + 1
ld h, (hl)
C$someheader.h$9$2_0$19 = .
.globl C$someheader.h$9$2_0$19 <------------------- May clash with other source files
;someheader.h:9: p_struct->var2 = value;
ld l, a
inc hl
push hl
ld a, (_conditional_var)
dec a
pop hl
jr nz, 00102$
ld (hl), #0x01
ret
00102$:
;someheader.h:9: p_struct->var2 = value;
ld (hl), #0x00
ret
_THIS_WILL___NOT___EMIT_HEADER_SYMBOL_FOR_INLINE_FUNCS::
ld a, (_conditional_var)
dec a
jr nz, 00102$
;main.c:26: inline_func(my_struct, 1);
ld a, (_my_struct)
ld hl, #_my_struct + 1
ld h, (hl)
;someheader.h:9: p_struct->var2 = value;
ld l, a
inc hl
ld (hl), #0x01
C$main.c$26$2_0$29 = .
.globl C$main.c$26$2_0$29
;main.c:26: inline_func(my_struct, 1);
00102$:
;main.c:28: inline_func(my_struct, 0);
ld a, (_my_struct)
ld hl, #_my_struct + 1
ld h, (hl)
;someheader.h:9: p_struct->var2 = value;
ld l, a
inc hl
ld (hl), #0x00
...
probably, the "C source line" symbol format may be extended a bit, to avoid the possible clashing. for example, by adding the additional information about the caller point (file/line) alongside with the existing information.
or just completely disable the emission of those symbols for the internals of the inline functions - that result in loosing ability to step into them, similar to what is happening with macros.