5 ms·
One of my favorite bugs was due to inline assembly! I once wrote a function that would crash only if you passed it the immediate value 4. Like `foo(4);` A vari
by corysama 2y ago
One of my favorite bugs was due to inline assembly!
I once wrote a function that would crash only if you passed it the immediate value 4. Like `foo(4);` A variable containing 4 would not crash.
The function was calling the Intel assembly instruction to byte-swap an integer. These days there’s an intrinsic for that. But, this was long ago and was written for platforms that didn’t even support POSIX.
So, this function was serializing an integer to memory, byteswapping it in the process, and naturally advancing the destination pointer as well.
The bug was that I forgot to mark the input register as dirty when I set up the inline assembly.
An important thing to know about inline asm, and the reason it is so strongly discouraged these days, is that the compiler cannot see it. It’s a black box for the compiler with a limited set of manual hints — one of which I omitted. Therefore it really mucks up the compiler’s ability to pipeline instructions. And, that cancels out the performance benefits for short snippets of assembly.
Anyway… I didn’t mark the register as dirty. Therefore, the compiler was unaware that it had been modified.
So, if I called the function with a 4, it would run some black box inline assembly, write the value to memory, and then it needed to increment a pointer by the size of one integer. 4 bytes. How convenient! The compiler just saw that at compile time there was definitely a 4 in a certain register! And, the inline asm declaration didn’t declare that anything happened to that register, such as byteswapping it. So, let’s just use that “unmodified” register as a value to add to the pointer!
Advancing a pointer by a byteswapped 4 makes for a quite invalid pointer. And, technically it was the next call to the serialized that would crash when it tried to write another value to that pointer.
- PhilipRoman 2y agoSounds like this could be a nice extension to clang/gcc sanitizers, adding checks that certain registers are unmodified across inline assembly calls.
- exDM69 2y agoIn addition to the missing newlines, the inline assembly code is also not correctly marking that it clobbers memory. It is probably not an issue for the entry point function, but the lack of it could mean the compiler is allowed to do incorrect optimizations.
- P_I_Staker 2y ago> Therefore it really mucks up the compiler’s ability to pipeline instructions. And, that cancels out the performance benefits for short snippets of assembly. This seems very untrue. Compiler shouldn't pipeline anything. Pipelining is baked into the architecture. You can write only ASM and the pipeline will be used. The reason we don't use assembly is that it tends to perform worse than optimized C code, and C is more user friendly. Weirdly this can be true even for very small code snips, that will perform better via optimization; a lesson from computer engineering class. An additional problem is the bugs caused by not following the programming manual. You technically don't need to "mark" registers, unless you want to tie into your C variables (and there are other ways like parameter passing to ASM functions). You can simply keep it clean for the rest of the C code with the stack. C code gets turned into ASM. Typically, you can follow the same rules, and everything will always be fine. Many compilers have extended assembly syntax, but I'm wary of it. Seems like some of the modern embedded compilers don't use it, and/or it's not entirely reliable. P.S. I'm not even sure how true the thing about optimization is, since there ARE asm optimizations. In this day and age, asking for help making your asm more efficient seems silly tho. Really should only be used to dip into hardware, which rarely has performance considerations.
- Retr0id 2y agoCompilers are aware of pipelineing and aim to emit pipeline-friendly code for a particular target. https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html - ctrl-f for "sched" and "reorder" (or indeed, "pipeline") and you'll see some examples.
- P_I_Staker 2y agoYes there might be optimizations related to improving pipeline performance. It is not "pipelining" your instructions.
- Joker_vD 2y ago> Compiler shouldn't pipeline anything Yeah, tell it to the researchers in the 90s who spent that decade on teaching their compilers to properly schedule the instructions for maximum throughput, and unrolling the loops to so that all of the 32 registers could be used, and accounting for the available execution units to hide latency etc. Sure, x86 nowadays do most of that in the hardware but still, even today, moving the loads to as early as possible is beneficial for hiding their latency because even a hyper-speculative, out-of-order processor can't start executing an instruction it hasn't fetched yet.