Reputation: 53960
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 implementations, 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.
Upvotes: 2
Views: 1682
Reputation: 24857
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
Upvotes: 1
Reputation: 26171
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.
Upvotes: 0
Reputation: 3264
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.
Upvotes: 0
Reputation: 25278
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 :)
Upvotes: 1
Reputation: 24857
?? '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
Upvotes: 0