开发者

Assembly - Thread Safe Local Variables

I'm trying to have thread-safe local variables in an assembly program. I've searched on the net, but I haven't found anything simple.

I'm currently using GCC assembler, as the program is a mix of C code and assembly, but the final program will contain code for multiple-platforms / calling conventions.

For now, I've declared my variables using the .lcomm pseudo-op. As I understand it, those variables will be placed in the .bss section. So I guess they will be shared by all threads.

Is there a way to have a kind of TLS variables directly in assembly, or should I use platform-specific imp开发者_开发知识库lementations, like pthread or __declspec on Windows?

Hope it's clear enough. Don't hesitate to ask if more information is needed.

Thanks to everyone,

EDIT

Here's the code in question:

.lcomm  stack0, 8
.lcomm  stack1, 8

.globl _XSRuntime_CallMethod
_XSRuntime_CallMethod:

    pushq   %rbp
    movq    %rsp,   %rbp

    xor     %rax,   %rax

    popq    stack0( %rip )
    popq    stack1( %rip )

    callq   *%rdi

    pushq   stack1( %rip )
    pushq   stack0( %rip )

    leave
    ret

Basically, it's used to redirect a call to a C function.

The C prototype is:

extern uint64_t XSRuntime_CallMethod( void ( *m )( void * self, ... ), ... );

It takes a function pointer as first argument, hence the callq *%rdi, as I'm testing this with system V ABI.

The assembly code is very simple, and I'd like to keep it that way, so it can be easily maintainable.

The question is: how to make the stack0 and stack1 variables thread safe.


Not that familiar with assembler so:

.lcomm  stack0, 8
.lcomm  stack1, 8

.globl _XSRuntime_CallMethod
_XSRuntime_CallMethod:

    pushq   %rbp // save BP
    movq    %rsp,   %rbp // load BP with SP

    xor     %rax,   %rax  // clear AX

    popq    stack0( %rip )  // pop return address into STACK0
    popq    stack1( %rip )  // pop flags into stack1

    callq   *%rdi  // call the indirect procedure, so putting flags/return to         XSRuntime_CallMethod onto stack

    pushq   stack1( %rip ) // put caller flags onto stack
    pushq   stack0( %rip ) // put caller return onto stack

    leave // clean passed parameters from stack
    ret   // and back to caller

Is this how it works, yes??

If so, would it not be easier to just jump to the indirect procedure, rather than calling it? You then don't need any extra variables to hold the caller flags/return & the indirected procedure returns directly to the caller.

Just a suggestion - while since I did assembler.

If you have to store the caller address somewhere, dec the SP, (enter?), and use the stack frame. Anything else is likely to be thread-unsafe at some point.

Rgds, Martin

Well, with a TLS maybee not thread-unsafe, but what about any recursive calls? You end up with another stack in the TLS to cover this, so you may as well use the 'SP' stack

Martin


How do you think the compiler implements thread-local variables? Try compiling such a program with -S or /FAs and you'll see. Hint: it has to rely on OS-specific APIs or other details to get access to TLS storage. Sometimes the preparation steps are hidden in the CRT but there's no single way to do it.

For example, here's how recent MSVC does it:

_TLS    SEGMENT
?number@@3HA DD 01H DUP (?)             ; number
_TLS    ENDS
EXTRN   __tls_array:DWORD
EXTRN   __tls_index:DWORD
_TEXT   SEGMENT
[...]
mov eax, DWORD PTR __tls_index
mov ecx, DWORD PTR fs:__tls_array
mov edx, DWORD PTR [ecx+eax*4]
mov eax, DWORD PTR ?number@@3HA[edx]

As you can see, it uses special variables which are initialized by the CRT.

On recent Linux, GCC can use TLS-specific relocations:

.globl number
    .section    .tbss,"awT",@nobits
number:
    .zero   4
    .text
    [...]
    movl    %gs:number@NTPOFF, %eax

If you want portability, it's best not to rely on such OS-specific details but use a generic API such as pthread or use stack-based approach proposed by Martin. But I guess if you wanted portability you wouldn't use assembler :)


?? 'Classic' local variables, ie. parameters/variables/results accessed by stack offsets, are inherently thread-safe.

If you need a 'TLS' that is platform-agnostic, pass some suitable struct/class instance into all threads, either as the creation parameter, in a thread field before resuming all the threads, first message to the threads input queue or whatever...

Rgds, Martin


As previously mentioned, local variables (stack-based) are inherently thread-safe because each thread has its own stack.

A thread-safe variable which is reachable by all threads (not stack-based) is probably best implemented using a spin-lock (or the equivalent in the Windows NT engine, a critical section). Such a variable must be locked before access, accessed and then unlocked. One variant could be that reads are free, but writes must be framed by locking/unlocking.

AFAIK only compilers themselves don't implement thread-safe variables. Instead they provide lib functions which access the required OS functionality.


You should probably be using(placing calls to) the TlsAlloc & TlsFree (or their other OS equivalents) to do something like this. the returned indices can be stored in global set once, read-only vars for easy use.

Depending on what the vars hold and what the code using them does, you might be able to get away with atomic ops, but that might yield problems of its own.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜