2019-02-25 18:21:27 -05:00
|
|
|
section .text
|
|
|
|
|
|
|
|
extern gettid
|
|
|
|
extern sched_yield
|
2019-03-19 22:04:33 -04:00
|
|
|
extern g_longwaits
|
2019-02-25 18:21:27 -05:00
|
|
|
|
|
|
|
; This is the first use of assembly in this codebase, a valid question is WHY?
|
|
|
|
; The spinlock we implement here is performance critical, and simply put GCC
|
|
|
|
; emits awful code. The original C code is left in fastlock.cpp for reference
|
2019-03-02 16:47:27 -05:00
|
|
|
; and x-plat.
|
2019-02-25 18:21:27 -05:00
|
|
|
|
|
|
|
ALIGN 16
|
|
|
|
global fastlock_lock
|
|
|
|
fastlock_lock:
|
|
|
|
; RDI points to the struct:
|
|
|
|
; uint16_t active
|
|
|
|
; uint16_t avail
|
|
|
|
; int32_t m_pidOwner
|
|
|
|
; int32_t m_depth
|
|
|
|
|
|
|
|
; First get our TID and put it in ecx
|
2019-03-01 13:29:21 -05:00
|
|
|
push rdi ; we need our struct pointer (also balance the stack for the call)
|
|
|
|
call gettid ; get our thread ID (TLS is nasty in ASM so don't bother inlining)
|
|
|
|
mov esi, eax ; back it up in esi
|
2019-06-15 23:53:34 -04:00
|
|
|
pop rdi ; get our pointer back
|
2019-02-25 18:21:27 -05:00
|
|
|
|
2019-03-01 13:29:21 -05:00
|
|
|
cmp [rdi+4], esi ; Is the TID we got back the owner of the lock?
|
|
|
|
je .LLocked ; Don't spin in that case
|
2019-02-25 18:21:27 -05:00
|
|
|
|
2019-03-01 13:29:21 -05:00
|
|
|
xor eax, eax ; eliminate partial register dependency
|
|
|
|
inc eax ; we want to add one
|
|
|
|
lock xadd [rdi+2], ax ; do the xadd, ax contains the value before the addition
|
2019-06-15 23:53:34 -04:00
|
|
|
; ax now contains the ticket
|
2019-02-25 18:21:27 -05:00
|
|
|
ALIGN 16
|
2019-03-01 13:29:21 -05:00
|
|
|
.LLoop:
|
2019-06-15 23:53:34 -04:00
|
|
|
mov edx, [rdi]
|
|
|
|
cmp dx, ax ; is our ticket up?
|
2019-03-01 13:29:21 -05:00
|
|
|
je .LLocked ; leave the loop
|
|
|
|
pause
|
|
|
|
add ecx, 1000h ; Have we been waiting a long time? (oflow if we have)
|
|
|
|
; 1000h is set so we overflow on the 1024*1024'th iteration (like the C code)
|
|
|
|
jnc .LLoop ; If so, give up our timeslice to someone who's doing real work
|
2019-02-25 18:21:27 -05:00
|
|
|
; Like the compiler, you're probably thinking: "Hey! I should take these pushs out of the loop"
|
|
|
|
; But the compiler doesn't know that we rarely hit this, and when we do we know the lock is
|
|
|
|
; taking a long time to be released anyways. We optimize for the common case of short
|
|
|
|
; lock intervals. That's why we're using a spinlock in the first place
|
2019-06-15 23:53:34 -04:00
|
|
|
inc edx
|
|
|
|
cmp dx, ax
|
|
|
|
je .LLoop
|
|
|
|
dec edx ; restore the current ticket
|
|
|
|
.LFutexWait:
|
2019-02-25 18:21:27 -05:00
|
|
|
push rsi
|
|
|
|
push rax
|
2019-06-15 23:53:34 -04:00
|
|
|
; Setup the syscall args
|
|
|
|
; rdi ARG1 futex (already in rdi)
|
|
|
|
mov esi, (9 | 128) ; rsi ARG2 FUTEX_WAIT_BITSET_PRIVATE
|
|
|
|
; rdx ARG3 ticketT.u (already in edx)
|
|
|
|
xor r10d, r10d ; r10 ARG4 NULL
|
|
|
|
mov r8, rdi ; r8 ARG5 dup rdi
|
|
|
|
xor r9d, r9d
|
|
|
|
bts r9d, eax ; r9 ARG6 mask
|
|
|
|
mov eax, 202 ; sys_futex
|
|
|
|
; Do the syscall
|
|
|
|
lock or [rdi+12], r9d ; inform the unlocking thread we're waiting
|
|
|
|
syscall ; wait for the futex
|
|
|
|
not r9d ; convert our flag into a mask of bits not to touch
|
|
|
|
lock and [rdi+12], r9d ; clear the flag in the futex control mask
|
|
|
|
; cleanup and continue
|
2019-03-19 22:04:33 -04:00
|
|
|
mov rcx, g_longwaits
|
|
|
|
inc qword [rcx] ; increment our long wait counter
|
2019-06-15 23:53:34 -04:00
|
|
|
pop rax
|
|
|
|
pop rsi
|
2019-03-01 13:29:21 -05:00
|
|
|
xor ecx, ecx ; Reset our loop counter
|
|
|
|
jmp .LLoop ; Get back in the game
|
|
|
|
ALIGN 16
|
|
|
|
.LLocked:
|
|
|
|
mov [rdi+4], esi ; lock->m_pidOwner = gettid()
|
|
|
|
inc dword [rdi+8] ; lock->m_depth++
|
2019-02-25 18:21:27 -05:00
|
|
|
ret
|
|
|
|
|
|
|
|
ALIGN 16
|
|
|
|
global fastlock_trylock
|
|
|
|
fastlock_trylock:
|
|
|
|
; RDI points to the struct:
|
|
|
|
; uint16_t active
|
|
|
|
; uint16_t avail
|
|
|
|
; int32_t m_pidOwner
|
|
|
|
; int32_t m_depth
|
|
|
|
|
|
|
|
; First get our TID and put it in ecx
|
2019-03-01 13:29:21 -05:00
|
|
|
push rdi ; we need our struct pointer (also balance the stack for the call)
|
|
|
|
call gettid ; get our thread ID (TLS is nasty in ASM so don't bother inlining)
|
|
|
|
mov esi, eax ; back it up in esi
|
|
|
|
pop rdi ; get our pointer back
|
2019-02-25 18:21:27 -05:00
|
|
|
|
2019-03-01 13:29:21 -05:00
|
|
|
cmp [rdi+4], esi ; Is the TID we got back the owner of the lock?
|
|
|
|
je .LRecursive ; Don't spin in that case
|
2019-02-25 18:21:27 -05:00
|
|
|
|
2019-03-01 13:29:21 -05:00
|
|
|
mov eax, [rdi] ; get both active and avail counters
|
|
|
|
mov ecx, eax ; duplicate in ecx
|
|
|
|
ror ecx, 16 ; swap upper and lower 16-bits
|
|
|
|
cmp eax, ecx ; are the upper and lower 16-bits the same?
|
|
|
|
jnz .LAlreadyLocked ; If not return failure
|
2019-02-25 18:21:27 -05:00
|
|
|
|
|
|
|
; at this point we know eax+ecx have [avail][active] and they are both the same
|
2019-03-01 13:29:21 -05:00
|
|
|
add ecx, 10000h ; increment avail, ecx is now our wanted value
|
|
|
|
lock cmpxchg [rdi], ecx ; If rdi still contains the value in eax, put in ecx (inc avail)
|
|
|
|
jnz .LAlreadyLocked ; If Z is not set then someone locked it while we were preparing
|
|
|
|
xor eax, eax
|
|
|
|
inc eax ; return SUCCESS! (eax=1)
|
|
|
|
mov [rdi+4], esi ; lock->m_pidOwner = gettid()
|
|
|
|
mov dword [rdi+8], eax ; lock->m_depth = 1
|
|
|
|
ret
|
|
|
|
ALIGN 16
|
|
|
|
.LRecursive:
|
|
|
|
xor eax, eax
|
|
|
|
inc eax ; return SUCCESS! (eax=1)
|
|
|
|
inc dword [rdi+8] ; lock->m_depth++
|
2019-02-25 18:21:27 -05:00
|
|
|
ret
|
2019-03-01 13:29:21 -05:00
|
|
|
ALIGN 16
|
2019-02-25 18:21:27 -05:00
|
|
|
.LAlreadyLocked:
|
2019-03-01 13:29:21 -05:00
|
|
|
xor eax, eax ; return 0;
|
2019-02-25 18:21:27 -05:00
|
|
|
ret
|
2019-03-02 16:47:27 -05:00
|
|
|
|
|
|
|
ALIGN 16
|
|
|
|
global fastlock_unlock
|
|
|
|
fastlock_unlock:
|
|
|
|
; RDI points to the struct:
|
|
|
|
; uint16_t active
|
|
|
|
; uint16_t avail
|
|
|
|
; int32_t m_pidOwner
|
|
|
|
; int32_t m_depth
|
2019-06-15 23:53:34 -04:00
|
|
|
push r11
|
2019-03-02 16:47:27 -05:00
|
|
|
sub dword [rdi+8], 1 ; decrement m_depth, don't use dec because it partially writes the flag register and we don't know its state
|
|
|
|
jnz .LDone ; if depth is non-zero this is a recursive unlock, and we still hold it
|
|
|
|
mov dword [rdi+4], -1 ; pidOwner = -1 (we don't own it anymore)
|
2019-06-15 23:53:34 -04:00
|
|
|
mov ecx, [rdi] ; get current active (this one)
|
|
|
|
inc ecx ; bump it to the next thread
|
|
|
|
mov [rdi], cx ; give up our ticket (note: lock is not required here because the spinlock itself guards this variable)
|
|
|
|
; At this point the lock is removed, however we must wake up any pending futexs
|
|
|
|
mov r9d, 1 ; eax is the bitmask for 2 threads
|
|
|
|
rol r9d, cl ; place the mask in the right spot for the next 2 threads
|
|
|
|
ALIGN 16
|
|
|
|
.LRetryWake:
|
|
|
|
mov r11d, [rdi+12] ; load the futex mask
|
|
|
|
and r11d, r9d ; are any threads waiting on a futex?
|
|
|
|
jz .LDone ; if not we're done.
|
|
|
|
; we have to wake the futexs
|
|
|
|
; rdi ARG1 futex (already in rdi)
|
|
|
|
mov esi, (10 | 128) ; rsi ARG2 FUTEX_WAKE_BITSET_PRIVATE
|
|
|
|
mov edx, 0x7fffffff ; rdx ARG3 INT_MAX (number of threads to wake)
|
|
|
|
xor r10d, r10d ; r10 ARG4 NULL
|
|
|
|
mov r8, rdi ; r8 ARG5 dup rdi
|
|
|
|
; r9 ARG6 mask (already set above)
|
|
|
|
mov eax, 202 ; sys_futex
|
|
|
|
syscall
|
|
|
|
cmp eax, 1 ; did we wake as many as we expected?
|
|
|
|
jnz .LRetryWake
|
2019-03-02 16:47:27 -05:00
|
|
|
.LDone:
|
2019-06-15 23:53:34 -04:00
|
|
|
pop r11
|
2019-03-02 16:47:27 -05:00
|
|
|
ret
|