GGC's inline assembly can be difficult to implement properly and easy to get wrong1. From a higher level perspective inline assembly has some rules that have to be considered outside of what instructions an inline assembly statement may emit.
The C/C++ standards consider asm
to be an option and implementation defined. Implementation defined behaviour is documented in GCC to include this:
Do not expect a sequence of asm statements to remain perfectly consecutive after compilation, even when you are using the volatile qualifier. If certain instructions need to remain consecutive in the output, put them in a single multi-instruction asm statement.
Basic inline assembly, or extended inline assembly without any output constraints are implicitly volatile
. The documentation says that being volatile doesn't guarantee that successive statements will be ordered as the appear in the source code. This code would not have a guaranteed order:
asm ("cli");
asm ("mov $'M', %%al; out %%al, $0xe9" ::: "eax");
asm ("mov $'D', %%al; out %%al, $0xe9" ::: "eax");
asm ("mov $'P', %%al; out %%al, $0xe9" ::: "eax");
asm ("sti");
If the intention is to use CLI and STI to turn off (and turn back on) external interrupts and output some letters in the order MDP
to the QEMU debug console (port 0xe9) then this isn't guaranteed. You can place all of them in a single inline assembly statement or you could use extended inline assembly templates to pass a dummy dependency to each statement guaranteeing ordering.
To make things more manageable OS developers in particular are known to create convenient wrappers around such code. Some developers do it as C pre-processor macros. In theory this looks useful:
#define outb(port, value) \
asm ("out %0, %1" \
: \
: "a"((uint8_t)value), "Nd"((uint16_t)port))
#define cli() asm ("cli")
#define sti() asm ("sti")
You can then use them like this:
cli ();
outb (0xe9, 'M');
outb (0xe9, 'D');
outb (0xe9, 'P');
sti ();
Of course the C pre-processor is done first before the C compiler begins to process the code itself. The pre-processor will generate these statements all in a row which is also not guaranteed to be emitted in a particular order by the code generator:
asm ("cli");
asm ("out %0, %1" : : "a"((uint8_t)'M'), "Nd"((uint16_t)0xe9));
asm ("out %0, %1" : : "a"((uint8_t)'D'), "Nd"((uint16_t)0xe9));
asm ("out %0, %1" : : "a"((uint8_t)'P'), "Nd"((uint16_t)0xe9));
asm ("sti");
My Questions
Some developers have taken it upon themselves to use macros that place the inline assembly statements inside a compound statement like this:
#define outb(port, value) ({ \
asm ("out %0, %1" \
: \
: "a"((uint8_t)value), "Nd"((uint16_t)port)); \
})
#define cli() ({ \
asm ("cli"); \
})
#define sti() ({ \
asm ("sti"); \
})
Using these macros as we did before would have the C pre-processor generating this code:
({ asm ("cli"); });
({ asm ("out %0, %1" : : "a"((uint8_t)'M'), "Nd"((uint16_t)0xe9)); });
({ asm ("out %0, %1" : : "a"((uint8_t)'D'), "Nd"((uint16_t)0xe9)); });
({ asm ("out %0, %1" : : "a"((uint8_t)'P'), "Nd"((uint16_t)0xe9)); });
({ asm ("sti"); });
Question 1: Does placing asm
statements inside a compound statement guarantee ordering? My view has been that I don't believe so, but I'm actually unsure. It is one of the reasons I avoid using pre-processor macros to generate inline assembly that I may use in a sequence like this.
For years I have used static inline
functions in headers for inline assembly statements. Functions provide type checking, but I also believed that inline assembly in functions does guarantee that the side effects (including inline assembly) are emitted by the next sequence point (the ;
on the end of a function call).
If I were to call actual functions my expectation is that each of these functions would have the inline assembly statements generated in order relative to one another:
cli ();
outb (0xe9, 'M');
outb (0xe9, 'D');
outb (0xe9, 'P');
sti ();
Question 2 : Does placing the inline assembly statements in actual functions (whether external linkage or inlined) guarantee the order? My feeling is that if this weren't the case that code like:
printf ("hello ");
printf ("world ");
Could be output as hello world
or world hello
. C's as-if rule suggests that optimizations can't alter observable behaviour. I believed the compiler wouldn't be able to assume that inline assembly actually had altered observable behaviour or not, so the compiler wouldn't be permitted to emit the inline assembly of the functions in another order.
asm()
statements might be to have macros that expand to just a string literal, to be used inside an likeasm(cli() outb(0xe9, 'M') sti());
Probably with each macro on a separate line. C string-literal concatenation takes care of joining it up into one big asm template. (Only works for compile-time-constant inputs, though; this doesn't give a mechanism for generating constraints for Extended asm.) – Enviable