Code: Select all
int current_task;
asm( "ltr %[output]"
: [output] "=r" (current_task)
);
should probably be
Code: Select all
int current_task;
asm( "str %[output]"
: [output] "=r" (current_task)
);
You store the task register, you don't load it
And another one:
I guess there is no output operand here, but an input operand instead.
As for the original topic, I like the way GCC handles inline assembly. GCC's operand constraint syntax offers the possibility to let the compiler choose which register(s) to use from a given set of registers, or choose memory or constant operands instead. The compiler can thus optimize the assembler code as well, which would not be possible if it was located in a separate file with fixed registers.
Of course there are some drawbacks with inline assembly. The first one is that inline assembly syntax is highly compiler dependent, so before using it, one must choose a compiler. This is not a major drawback for me, since I use GCC most of the time, as it fits my needs: It is fast, optimizing, and targets plenty different architectures. And of course it is open source and so on.
Another drawback that has been mentioned in this thread is that inline assembly is not portable between different target architectures. Of course this is true. This is the reason why I use inline assembly only for architecture specific code. (A typical example is x86 paging code: Of course, an instruction like "movl %0, %%cr3" works only for x86 or x86_64, but this paging code will be used only for these, anyway.) There are some situations where a similar operation can be performed on different architectures, but with different instructions. An example for this are spinlocks. Most architectures support an atomic test-and-set instruction, but these may look completely different. In this case, I use something similar to this:
Code: Select all
// AtomicLock.h
#include <config.h>
class AtomicLock
{
private:
volatile int key;
public:
void Enter(void);
void Exit(void);
};
#include INC_ARCH(AtomicLock.h)
Here, INC_ARCH is defined in config.h, which is generated by configure, and points to some architecture specific include directory, like this:
Finally, arch/x86/AtomicLock.h contains the architecture specific code:
Code: Select all
// arch/x86/AtomicLock.h
inline void AtomicLock::Enter(void)
{
asm volatile ("1: \n"
"pause \n"
"lock btsl $0, %[key] \n"
"jc 1b" : : [key] "m" (key));
}
inline void AtomicLock::Exit(void)
{
asm volatile ("movl $0, %[key]" : : [key] "m" (key));
}
On a different architecture (say, M68K) it looks completely different:
Code: Select all
// arch/m68k/AtomicLock.h
inline void AtomicLock::Enter(void)
{
asm volatile ("1: \n"
"bsetb #0, %[key] \n"
"beqs 1b" : : [key] "m" (key));
}
inline void AtomicLock::Exit(void)
{
asm volatile ("movel #0, %[key]" : : [key] "m" (key));
}
(Note that constants look like $0 in x86 assembly, but #0 in M68K assembly.)