GCC inline assembler: какой вариант кода более корректный?
Я давно откладывал вопрос применения встроенного ассемблера в своих программах на GNU C++, но наконец решил, что пора. И вот, я имею два варианта написания функций для синхронизации потоков через атомарные флаги.
Первый вариант:
INLINE void AtomicFlagLock(UINT* pFlagsDword, UINT iBit)
{
asm volatile (
".intel_syntax noprefix\n"
"0: lock bts dword ptr %0, %1 \n"
" jc 0b \n"
:
:"m"(*pFlagsDword), "ri"(iBit)
);
}
INLINE void AtomicFlagUnlock(UINT* pFlagsDword, UINT iBit)
{
asm volatile (
".intel_syntax noprefix\n"
" lock btr dword ptr %0, %1 \n"
:
:"m"(*pFlagsDword), "ri"(iBit)
);
}
Второй вариант:
INLINE void AtomicFlagLock(UINT* pFlagsDword, UINT iBit)
{
asm volatile (
".intel_syntax noprefix\n"
"0: lock bts dword ptr %0, %1 \n"
" jc 0b \n"
:"+m"(*pFlagsDword)
:"ri"(iBit)
);
}
INLINE void AtomicFlagUnlock(UINT* pFlagsDword, UINT iBit)
{
asm volatile (
".intel_syntax noprefix\n"
" lock btr dword ptr %0, %1 \n"
:"+m"(*pFlagsDword)
:"ri"(iBit)
);
}
Собственно, мой вопрос: какой из этих вариантов более корректный? В тестовой программе код генерируется в обоих случаях одинаковый и верный. Однако, насколько мне удалось правильно постичь логику оператора asm() в GCC, второй вариант кода этих функций, где выражение *pFlagsDword объявляется в секции output с модификатором "+m", мне кажется более предпочтительным, так как в этом случае компилятору явно указывается, что значение *pFlagsDword в блоке asm() модифицируется, в то время как в первом варианте компилятор об этом не знает и в теории может что-то накуролесить с оптимизацией. Я прав, или не очень?
Так как тема новая для меня, мне будут полезны ваши ответы, советы и замечания по выше приведённому коду.
Вот тестовая программа:
#define INLINE __attribute__ ((always_inline)) inline
#define NOINLINE __attribute__ ((noinline))
typedef unsigned int UINT;
INLINE void AtomicFlagLock(UINT* pFlagsDword, UINT iBit)
{
asm volatile (
".intel_syntax noprefix\n"
"0: lock bts dword ptr %0, %1 \n"
" jc 0b \n"
:"+m"(*pFlagsDword)
:"ri"(iBit)
);
}
INLINE void AtomicFlagUnlock(UINT* pFlagsDword, UINT iBit)
{
asm volatile (
".intel_syntax noprefix\n"
" lock btr dword ptr %0, %1 \n"
:"+m"(*pFlagsDword)
:"ri"(iBit)
);
}
void lock_fl(UINT* pfl, UINT iBit);
void unlock_fl(UINT* pfl, UINT iBit);
int main()
{
UINT fl = 0;
AtomicFlagLock(&fl,2);
lock_fl(&fl,3);
AtomicFlagUnlock(&fl,3);
unlock_fl(&fl,2);
return 0;
}
NOINLINE void lock_fl(UINT* pfl, UINT iBit)
{
AtomicFlagLock(pfl,iBit);
}
NOINLINE void unlock_fl(UINT* pfl, UINT iBit)
{
AtomicFlagUnlock(pfl,iBit);
}
Вот згенерированный компилятором код (одинаковый для обоих вариантов):
.text:00403BB0 ; =============== S U B R O U T I N E =======================================
.text:00403BB0
.text:00403BB0 ; Attributes: bp-based frame
.text:00403BB0
.text:00403BB0 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00403BB0 public _main
.text:00403BB0 _main proc near ; CODE XREF: sub_4011A0+91↑p
.text:00403BB0
.text:00403BB0 fl = dword ptr -4
.text:00403BB0 argc = dword ptr 8
.text:00403BB0 argv = dword ptr 0Ch
.text:00403BB0 envp = dword ptr 10h
.text:00403BB0
.text:00403BB0 ; __unwind {
.text:00403BB0 push ebp
.text:00403BB1 mov ebp, esp
.text:00403BB3 and esp, 0FFFFFFF0h
.text:00403BB6 sub esp, 20h
.text:00403BB9 call ___main
.text:00403BBE mov [esp+20h+fl], 0
.text:00403BC6
.text:00403BC6 L0: ; CODE XREF: _main+1D↓j
.text:00403BC6 lock bts [esp+20h+fl], 2
.text:00403BCD jb short L0
.text:00403BCF lea ecx, [esp+20h+fl]
.text:00403BD3 mov dword ptr [esp+4], 3 ; iBit
.text:00403BDB mov [esp], ecx ; pfl
.text:00403BDE call __Z7lock_flPjj ; lock_fl(uint *,uint)
.text:00403BE3 lock btr [esp+20h+fl], 3
.text:00403BEA mov dword ptr [esp+4], 2 ; iBit
.text:00403BF2 mov [esp], ecx ; pfl
.text:00403BF5 call __Z9unlock_flPjj ; unlock_fl(uint *,uint)
.text:00403BFA xor eax, eax
.text:00403BFC leave
.text:00403BFD retn
.text:00403BFD ; } // starts at 403BB0
.text:00403BFD _main endp
.text:00401410 ; =============== S U B R O U T I N E =======================================
.text:00401410
.text:00401410
.text:00401410 ; _DWORD __cdecl lock_fl(unsigned int *pfl, unsigned int iBit)
.text:00401410 public __Z7lock_flPjj
.text:00401410 __Z7lock_flPjj proc near ; CODE XREF: _main+2E↓p
.text:00401410
.text:00401410 pfl = dword ptr 4
.text:00401410 iBit = dword ptr 8
.text:00401410
.text:00401410 ; __unwind {
.text:00401410 mov eax, [esp+pfl]
.text:00401414 mov edx, [esp+iBit]
.text:00401418
.text:00401418 loc_401418: ; CODE XREF: lock_fl(uint *,uint)+C↓j
.text:00401418 lock bts [eax], edx
.text:0040141C jb short loc_401418
.text:0040141E retn
.text:0040141E ; } // starts at 401410
.text:0040141E __Z7lock_flPjj endp
.text:0040141E
.text:0040141E ; ---------------------------------------------------------------------------
.text:0040141F align 10h
.text:00401420
.text:00401420 ; =============== S U B R O U T I N E =======================================
.text:00401420
.text:00401420
.text:00401420 ; _DWORD __cdecl unlock_fl(unsigned int *pfl, unsigned int iBit)
.text:00401420 public __Z9unlock_flPjj
.text:00401420 __Z9unlock_flPjj proc near ; CODE XREF: _main+45↓p
.text:00401420
.text:00401420 pfl = dword ptr 4
.text:00401420 iBit = dword ptr 8
.text:00401420
.text:00401420 ; __unwind {
.text:00401420 mov eax, [esp+pfl]
.text:00401424 mov edx, [esp+iBit]
.text:00401428 lock btr [eax], edx
.text:0040142C retn
.text:0040142C ; } // starts at 401420
.text:0040142C __Z9unlock_flPjj endp