[pypy-commit] pypy default: hg merge fast-gil

arigo noreply at buildbot.pypy.org
Wed Jun 25 21:35:51 CEST 2014


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r72234:dd8a43cdd8d5
Date: 2014-06-25 21:33 +0200
http://bitbucket.org/pypy/pypy/changeset/dd8a43cdd8d5/

Log:	hg merge fast-gil

	A faster way to handle the GIL, particularly in JIT code. The GIL
	is now a composite of two concepts: a global number (it's just set
	from 1 to 0 and back around CALL_RELEASE_GIL), and a real mutex. If
	there are threads waiting to acquire the GIL, one of them is
	actively checking the global number every 0.1 ms to 1 ms.

diff --git a/pypy/module/thread/gil.py b/pypy/module/thread/gil.py
--- a/pypy/module/thread/gil.py
+++ b/pypy/module/thread/gil.py
@@ -7,7 +7,7 @@
 # all but one will be blocked.  The other threads get a chance to run
 # from time to time, using the periodic action GILReleaseAction.
 
-from rpython.rlib import rthread
+from rpython.rlib import rthread, rgil
 from pypy.module.thread.error import wrap_thread_error
 from pypy.interpreter.executioncontext import PeriodicAsyncAction
 from pypy.module.thread.threadlocals import OSThreadLocals
@@ -25,8 +25,7 @@
                                                   use_bytecode_counter=True)
 
     def _initialize_gil(self, space):
-        if not rthread.gil_allocate():
-            raise wrap_thread_error(space, "can't allocate GIL")
+        rgil.gil_allocate()
 
     def setup_threads(self, space):
         """Enable threads in the object space, if they haven't already been."""
@@ -71,15 +70,13 @@
 def before_external_call():
     # this function must not raise, in such a way that the exception
     # transformer knows that it cannot raise!
-    e = get_errno()
-    rthread.gil_release()
-    set_errno(e)
+    rgil.gil_release()
 before_external_call._gctransformer_hint_cannot_collect_ = True
 before_external_call._dont_reach_me_in_del_ = True
 
 def after_external_call():
     e = get_errno()
-    rthread.gil_acquire()
+    rgil.gil_acquire()
     rthread.gc_thread_run()
     after_thread_switch()
     set_errno(e)
@@ -97,7 +94,7 @@
     # explicitly release the gil, in a way that tries to give more
     # priority to other threads (as opposed to continuing to run in
     # the same thread).
-    if rthread.gil_yield_thread():
+    if rgil.gil_yield_thread():
         rthread.gc_thread_run()
         after_thread_switch()
 do_yield_thread._gctransformer_hint_close_stack_ = True
diff --git a/rpython/jit/backend/llsupport/assembler.py b/rpython/jit/backend/llsupport/assembler.py
--- a/rpython/jit/backend/llsupport/assembler.py
+++ b/rpython/jit/backend/llsupport/assembler.py
@@ -303,28 +303,39 @@
 
     @staticmethod
     @rgc.no_collect
-    def _release_gil_asmgcc(css):
-        # similar to trackgcroot.py:pypy_asm_stackwalk, first part
-        from rpython.memory.gctransform import asmgcroot
-        new = rffi.cast(asmgcroot.ASM_FRAMEDATA_HEAD_PTR, css)
-        next = asmgcroot.gcrootanchor.next
-        new.next = next
-        new.prev = asmgcroot.gcrootanchor
-        asmgcroot.gcrootanchor.next = new
-        next.prev = new
-        # and now release the GIL
-        before = rffi.aroundstate.before
-        if before:
-            before()
+    def _reacquire_gil_asmgcc(css, old_rpy_fastgil):
+        # Before doing an external call, 'rpy_fastgil' is initialized to
+        # be equal to css.  This function is called if we find out after
+        # the call that it is no longer equal to css.  See description
+        # in translator/c/src/thread_pthread.c.
 
-    @staticmethod
-    @rgc.no_collect
-    def _reacquire_gil_asmgcc(css):
-        # first reacquire the GIL
-        after = rffi.aroundstate.after
-        if after:
-            after()
-        # similar to trackgcroot.py:pypy_asm_stackwalk, second part
+        if old_rpy_fastgil == 0:
+            # this case occurs if some other thread stole the GIL but
+            # released it again.  What occurred here is that we changed
+            # 'rpy_fastgil' from 0 to 1, thus successfully reaquiring the
+            # GIL.
+            pass
+
+        elif old_rpy_fastgil == 1:
+            # 'rpy_fastgil' was (and still is) locked by someone else.
+            # We need to wait for the regular mutex.
+            after = rffi.aroundstate.after
+            if after:
+                after()
+        else:
+            # stole the GIL from a different thread that is also
+            # currently in an external call from the jit.  Attach
+            # the 'old_rpy_fastgil' into the chained list.
+            from rpython.memory.gctransform import asmgcroot
+            oth = rffi.cast(asmgcroot.ASM_FRAMEDATA_HEAD_PTR, old_rpy_fastgil)
+            next = asmgcroot.gcrootanchor.next
+            oth.next = next
+            oth.prev = asmgcroot.gcrootanchor
+            asmgcroot.gcrootanchor.next = oth
+            next.prev = oth
+
+        # similar to trackgcroot.py:pypy_asm_stackwalk, second part:
+        # detach the 'css' from the chained list
         from rpython.memory.gctransform import asmgcroot
         old = rffi.cast(asmgcroot.ASM_FRAMEDATA_HEAD_PTR, css)
         prev = old.prev
@@ -334,42 +345,28 @@
 
     @staticmethod
     @rgc.no_collect
-    def _release_gil_shadowstack():
-        before = rffi.aroundstate.before
-        if before:
-            before()
-
-    @staticmethod
-    @rgc.no_collect
     def _reacquire_gil_shadowstack():
+        # Simplified version of _reacquire_gil_asmgcc(): in shadowstack mode,
+        # 'rpy_fastgil' contains only zero or non-zero, and this is only
+        # called when the old value stored in 'rpy_fastgil' was non-zero
+        # (i.e. still locked, must wait with the regular mutex)
         after = rffi.aroundstate.after
         if after:
             after()
 
-    @staticmethod
-    def _no_op():
-        pass
-
-    _NOARG_FUNC = lltype.Ptr(lltype.FuncType([], lltype.Void))
-    _CLOSESTACK_FUNC = lltype.Ptr(lltype.FuncType([rffi.LONGP],
-                                                  lltype.Void))
+    _REACQGIL0_FUNC = lltype.Ptr(lltype.FuncType([], lltype.Void))
+    _REACQGIL2_FUNC = lltype.Ptr(lltype.FuncType([rffi.CCHARP, lltype.Signed],
+                                                 lltype.Void))
 
     def _build_release_gil(self, gcrootmap):
-        if gcrootmap is None:
-            releasegil_func = llhelper(self._NOARG_FUNC, self._no_op)
-            reacqgil_func = llhelper(self._NOARG_FUNC, self._no_op)
-        elif gcrootmap.is_shadow_stack:
-            releasegil_func = llhelper(self._NOARG_FUNC,
-                                       self._release_gil_shadowstack)
-            reacqgil_func = llhelper(self._NOARG_FUNC,
+        if gcrootmap is None or gcrootmap.is_shadow_stack:
+            reacqgil_func = llhelper(self._REACQGIL0_FUNC,
                                      self._reacquire_gil_shadowstack)
+            self.reacqgil_addr = self.cpu.cast_ptr_to_int(reacqgil_func)
         else:
-            releasegil_func = llhelper(self._CLOSESTACK_FUNC,
-                                       self._release_gil_asmgcc)
-            reacqgil_func = llhelper(self._CLOSESTACK_FUNC,
+            reacqgil_func = llhelper(self._REACQGIL2_FUNC,
                                      self._reacquire_gil_asmgcc)
-        self.releasegil_addr  = self.cpu.cast_ptr_to_int(releasegil_func)
-        self.reacqgil_addr = self.cpu.cast_ptr_to_int(reacqgil_func)
+            self.reacqgil_addr = self.cpu.cast_ptr_to_int(reacqgil_func)
 
     def _is_asmgcc(self):
         gcrootmap = self.cpu.gc_ll_descr.gcrootmap
diff --git a/rpython/jit/backend/llsupport/callbuilder.py b/rpython/jit/backend/llsupport/callbuilder.py
--- a/rpython/jit/backend/llsupport/callbuilder.py
+++ b/rpython/jit/backend/llsupport/callbuilder.py
@@ -1,4 +1,7 @@
 from rpython.rlib.clibffi import FFI_DEFAULT_ABI
+from rpython.rlib import rgil
+from rpython.rtyper.lltypesystem import lltype, rffi
+
 
 class AbstractCallBuilder(object):
 
@@ -42,20 +45,21 @@
     def emit_call_release_gil(self):
         """Emit a CALL_RELEASE_GIL, including calls to releasegil_addr
         and reacqgil_addr."""
+        fastgil = rffi.cast(lltype.Signed, rgil.gil_fetch_fastgil())
         self.select_call_release_gil_mode()
         self.prepare_arguments()
         self.push_gcmap_for_call_release_gil()
-        self.call_releasegil_addr_and_move_real_arguments()
+        self.call_releasegil_addr_and_move_real_arguments(fastgil)
         self.emit_raw_call()
         self.restore_stack_pointer()
-        self.move_real_result_and_call_reacqgil_addr()
+        self.move_real_result_and_call_reacqgil_addr(fastgil)
         self.pop_gcmap()
         self.load_result()
 
-    def call_releasegil_addr_and_move_real_arguments(self):
+    def call_releasegil_addr_and_move_real_arguments(self, fastgil):
         raise NotImplementedError
 
-    def move_real_result_and_call_reacqgil_addr(self):
+    def move_real_result_and_call_reacqgil_addr(self, fastgil):
         raise NotImplementedError
 
     def select_call_release_gil_mode(self):
diff --git a/rpython/jit/backend/llsupport/test/test_gc_integration.py b/rpython/jit/backend/llsupport/test/test_gc_integration.py
--- a/rpython/jit/backend/llsupport/test/test_gc_integration.py
+++ b/rpython/jit/backend/llsupport/test/test_gc_integration.py
@@ -2,6 +2,7 @@
 """ Tests for register allocation for common constructs
 """
 
+import py
 import re
 from rpython.jit.metainterp.history import TargetToken, BasicFinalDescr,\
      JitCellToken, BasicFailDescr, AbstractDescr
@@ -780,6 +781,9 @@
         assert rffi.cast(JITFRAMEPTR, cpu.gc_ll_descr.write_barrier_on_frame_called) == frame
 
     def test_call_release_gil(self):
+        py.test.skip("xxx fix this test: the code is now assuming that "
+                     "'before' is just rgil.release_gil(), and 'after' is "
+                     "only needed if 'rpy_fastgil' was not changed.")
         # note that we can't test floats here because when untranslated
         # people actually wreck xmm registers
         cpu = self.cpu
diff --git a/rpython/jit/backend/x86/callbuilder.py b/rpython/jit/backend/x86/callbuilder.py
--- a/rpython/jit/backend/x86/callbuilder.py
+++ b/rpython/jit/backend/x86/callbuilder.py
@@ -25,9 +25,6 @@
     # arguments, we need to decrease esp temporarily
     stack_max = PASS_ON_MY_FRAME
 
-    # set by save_result_value()
-    tmpresloc = None
-
     def __init__(self, assembler, fnloc, arglocs,
                  resloc=eax, restype=INT, ressize=WORD):
         AbstractCallBuilder.__init__(self, assembler, fnloc, arglocs,
@@ -41,7 +38,6 @@
         self.current_esp = 0     # 0 or (usually) negative, counted in bytes
 
     def select_call_release_gil_mode(self):
-        """Overridden in CallBuilder64"""
         AbstractCallBuilder.select_call_release_gil_mode(self)
         if self.asm._is_asmgcc():
             from rpython.memory.gctransform import asmgcroot
@@ -69,12 +65,10 @@
         if self.ressize == 0:
             return      # void result
         # use the code in load_from_mem to do the zero- or sign-extension
-        srcloc = self.tmpresloc
-        if srcloc is None:
-            if self.restype == FLOAT:
-                srcloc = xmm0
-            else:
-                srcloc = eax
+        if self.restype == FLOAT:
+            srcloc = xmm0
+        else:
+            srcloc = eax
         if self.ressize >= WORD and self.resloc is srcloc:
             return      # no need for any MOV
         if self.ressize == 1 and isinstance(srcloc, RegLoc):
@@ -100,13 +94,14 @@
             self.asm.set_extra_stack_depth(self.mc, 0)
         self.asm.pop_gcmap(self.mc)
 
-    def call_releasegil_addr_and_move_real_arguments(self):
-        initial_esp = self.current_esp
-        self.save_register_arguments()
+    def call_releasegil_addr_and_move_real_arguments(self, fastgil):
+        from rpython.jit.backend.x86.assembler import heap
         #
         if not self.asm._is_asmgcc():
-            # the helper takes no argument
+            # shadowstack: change 'rpy_fastgil' to 0 (it should be
+            # non-zero right now).
             self.change_extra_stack_depth = False
+            css_value = imm(0)
         else:
             from rpython.memory.gctransform import asmgcroot
             # build a 'css' structure on the stack: 2 words for the linkage,
@@ -120,73 +115,95 @@
             index_of_ebp = css + WORD * (2+asmgcroot.INDEX_OF_EBP)
             self.mc.MOV_sr(index_of_ebp, ebp.value)  # MOV [css.ebp], EBP
             # Save the "return address": we pretend that it's css
-            if IS_X86_32:
-                reg = eax
-            elif IS_X86_64:
-                reg = edi
-            self.mc.LEA_rs(reg.value, css)           # LEA reg, [css]
+            self.mc.LEA_rs(eax.value, css)           # LEA eax, [css]
             frame_ptr = css + WORD * (2+asmgcroot.FRAME_PTR)
-            self.mc.MOV_sr(frame_ptr, reg.value)     # MOV [css.frame], reg
+            self.mc.MOV_sr(frame_ptr, eax.value)     # MOV [css.frame], eax
             # Set up jf_extra_stack_depth to pretend that the return address
             # was at css, and so our stack frame is supposedly shorter by
             # (PASS_ON_MY_FRAME-JIT_USE_WORDS+1) words
             delta = PASS_ON_MY_FRAME - asmgcroot.JIT_USE_WORDS + 1
             self.change_extra_stack_depth = True
             self.asm.set_extra_stack_depth(self.mc, -delta * WORD)
-            # Call the closestack() function (also releasing the GIL)
-            # with 'reg' as argument
-            if IS_X86_32:
-                self.subtract_esp_aligned(1)
-                self.mc.MOV_sr(0, reg.value)
-            #else:
-            #   on x86_64, reg is edi so that it is already correct
+            css_value = eax
         #
-        self.mc.CALL(imm(self.asm.releasegil_addr))
+        self.mc.MOV(heap(fastgil), css_value)
         #
         if not we_are_translated():        # for testing: we should not access
-            self.mc.ADD(ebp, imm(1))       # ebp any more
+            self.mc.ADD(ebp, imm(1))       # ebp any more; and ignore 'fastgil'
+
+    def move_real_result_and_call_reacqgil_addr(self, fastgil):
+        from rpython.jit.backend.x86.assembler import heap
+        from rpython.jit.backend.x86 import rx86
         #
-        self.restore_register_arguments()
-        self.restore_stack_pointer(initial_esp)
-
-    def save_register_arguments(self):
-        """Overridden in CallBuilder64"""
-
-    def restore_register_arguments(self):
-        """Overridden in CallBuilder64"""
-
-    def move_real_result_and_call_reacqgil_addr(self):
-        # save the result we just got (in eax/eax+edx/st(0)/xmm0)
-        self.save_result_value()
-        # call the reopenstack() function (also reacquiring the GIL)
+        # check if we need to call the reacqgil() function or not
+        # (to acquiring the GIL, remove the asmgcc head from
+        # the chained list, etc.)
+        mc = self.mc
+        restore_edx = False
         if not self.asm._is_asmgcc():
-            css = 0     # the helper takes no argument
+            css = 0
+            css_value = imm(0)
+            old_value = ecx
         else:
             from rpython.memory.gctransform import asmgcroot
             css = WORD * (PASS_ON_MY_FRAME - asmgcroot.JIT_USE_WORDS)
             if IS_X86_32:
-                reg = eax
+                assert css >= 16
+                if self.restype == 'L':    # long long result: eax/edx
+                    mc.MOV_sr(12, edx.value)
+                    restore_edx = True
+                css_value = edx
+                old_value = ecx
             elif IS_X86_64:
-                reg = edi
-            self.mc.LEA_rs(reg.value, css)
+                css_value = edi
+                old_value = esi
+            mc.LEA_rs(css_value.value, css)
+        #
+        mc.MOV(old_value, imm(1))
+        if rx86.fits_in_32bits(fastgil):
+            mc.XCHG_rj(old_value.value, fastgil)
+        else:
+            mc.MOV_ri(X86_64_SCRATCH_REG.value, fastgil)
+            mc.XCHG_rm(old_value.value, (X86_64_SCRATCH_REG.value, 0))
+        mc.CMP(old_value, css_value)
+        mc.J_il8(rx86.Conditions['E'], 0)
+        je_location = mc.get_relative_pos()
+        #
+        # Yes, we need to call the reacqgil() function
+        self.save_result_value_reacq()
+        if self.asm._is_asmgcc():
             if IS_X86_32:
-                self.mc.MOV_sr(0, reg.value)
+                mc.MOV_sr(4, old_value.value)
+                mc.MOV_sr(0, css_value.value)
+            # on X86_64, they are already in the right registers
+        mc.CALL(imm(self.asm.reacqgil_addr))
+        self.restore_result_value_reacq()
         #
-        self.mc.CALL(imm(self.asm.reacqgil_addr))
+        # patch the JE above
+        offset = mc.get_relative_pos() - je_location
+        assert 0 < offset <= 127
+        mc.overwrite(je_location-1, chr(offset))
         #
-        if not we_are_translated():        # for testing: now we can accesss
-            self.mc.SUB(ebp, imm(1))       # ebp again
+        if restore_edx:
+            mc.MOV_rs(edx.value, 12)   # restore this
+        #
+        if not we_are_translated():    # for testing: now we can accesss
+            mc.SUB(ebp, imm(1))        # ebp again
         #
         # Now that we required the GIL, we can reload a possibly modified ebp
         if self.asm._is_asmgcc():
             # special-case: reload ebp from the css
             from rpython.memory.gctransform import asmgcroot
             index_of_ebp = css + WORD * (2+asmgcroot.INDEX_OF_EBP)
-            self.mc.MOV_rs(ebp.value, index_of_ebp)  # MOV EBP, [css.ebp]
+            mc.MOV_rs(ebp.value, index_of_ebp)  # MOV EBP, [css.ebp]
         #else:
         #   for shadowstack, done for us by _reload_frame_if_necessary()
 
-    def save_result_value(self):
+    def save_result_value_reacq(self):
+        """Overridden in CallBuilder32 and CallBuilder64"""
+        raise NotImplementedError
+
+    def restore_result_value_reacq(self):
         """Overridden in CallBuilder32 and CallBuilder64"""
         raise NotImplementedError
 
@@ -239,58 +256,71 @@
         resloc = self.resloc
         if resloc is not None and resloc.is_float():
             # a float or a long long return
-            if self.tmpresloc is None:
-                if self.restype == 'L':     # long long
-                    # move eax/edx -> xmm0
-                    self.mc.MOVD32_xr(resloc.value^1, edx.value)
-                    self.mc.MOVD32_xr(resloc.value,   eax.value)
-                    self.mc.PUNPCKLDQ_xx(resloc.value, resloc.value^1)
-                else:
-                    # float: we have to go via the stack
-                    self.mc.FSTPL_s(0)
-                    self.mc.MOVSD_xs(resloc.value, 0)
+            if self.restype == 'L':     # long long
+                # move eax/edx -> xmm0
+                self.mc.MOVD32_xr(resloc.value^1, edx.value)
+                self.mc.MOVD32_xr(resloc.value,   eax.value)
+                self.mc.PUNPCKLDQ_xx(resloc.value, resloc.value^1)
             else:
-                self.mc.MOVSD(resloc, self.tmpresloc)
+                # float: we have to go via the stack
+                self.mc.FSTPL_s(0)
+                self.mc.MOVSD_xs(resloc.value, 0)
             #
         elif self.restype == 'S':
             # singlefloat return: must convert ST(0) to a 32-bit singlefloat
             # and load it into self.resloc.  mess mess mess
-            if self.tmpresloc is None:
-                self.mc.FSTPS_s(0)
-                self.mc.MOV_rs(resloc.value, 0)
-            else:
-                self.mc.MOV(resloc, self.tmpresloc)
+            self.mc.FSTPS_s(0)
+            self.mc.MOV_rs(resloc.value, 0)
         else:
             CallBuilderX86.load_result(self)
 
-    def save_result_value(self):
-        # Temporarily save the result value into [ESP+4].  We use "+4"
-        # in order to leave the word at [ESP+0] free, in case it's needed
+    def save_result_value_reacq(self):
+        # Temporarily save the result value into [ESP+8].  We use "+8"
+        # in order to leave the two initial words free, in case it's needed.
+        # Also note that in this 32-bit case, a long long return value is
+        # in eax/edx, but we already saved the value of edx in
+        # move_real_result_and_call_reacqgil_addr().
         if self.ressize == 0:      # void return
             return
         if self.resloc.is_float():
             # a float or a long long return
-            self.tmpresloc = RawEspLoc(4, FLOAT)
             if self.restype == 'L':
-                self.mc.MOV_sr(4, eax.value)      # long long
-                self.mc.MOV_sr(8, edx.value)
+                self.mc.MOV_sr(8, eax.value)      # long long
+                #self.mc.MOV_sr(12, edx.value) -- already done by the caller
             else:
-                self.mc.FSTPL_s(4)                # float return
+                self.mc.FSTPL_s(8)                # float return
         else:
-            self.tmpresloc = RawEspLoc(4, INT)
             if self.restype == 'S':
-                self.mc.FSTPS_s(4)
+                self.mc.FSTPS_s(8)
             else:
                 assert self.restype == INT
                 assert self.ressize <= WORD
-                self.mc.MOV_sr(4, eax.value)
+                self.mc.MOV_sr(8, eax.value)
+
+    def restore_result_value_reacq(self):
+        # Opposite of save_result_value_reacq()
+        if self.ressize == 0:      # void return
+            return
+        if self.resloc.is_float():
+            # a float or a long long return
+            if self.restype == 'L':
+                self.mc.MOV_rs(eax.value, 8)      # long long
+                #self.mc.MOV_rs(edx.value, 12) -- will be done by the caller
+            else:
+                self.mc.FLDL_s(8)                 # float return
+        else:
+            if self.restype == 'S':
+                self.mc.FLDS_s(8)
+            else:
+                assert self.restype == INT
+                assert self.ressize <= WORD
+                self.mc.MOV_rs(eax.value, 8)
 
 
 class CallBuilder64(CallBuilderX86):
 
     ARGUMENTS_GPR = [edi, esi, edx, ecx, r8, r9]
     ARGUMENTS_XMM = [xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7]
-    DONT_MOVE_GPR = []
     _ALL_CALLEE_SAVE_GPR = [ebx, r12, r13, r14, r15]
 
     next_arg_gpr = 0
@@ -303,13 +333,6 @@
             res = self.ARGUMENTS_GPR[i]
         except IndexError:
             return None
-        if hint in self.DONT_MOVE_GPR:
-            for j in range(i):
-                if hint is self.ARGUMENTS_GPR[j]:
-                    break
-            else:
-                self.ARGUMENTS_GPR[i] = hint
-                res = hint
         return res
 
     def _unused_xmm(self):
@@ -320,51 +343,6 @@
         except IndexError:
             return None
 
-    def _permute_to_prefer_unused_registers(self, lst):
-        # permute 'lst' so that it starts with registers that are not
-        # in 'self.already_used', and ends with registers that are.
-        N = len(lst)
-        i = 0
-        while i < N:
-            reg = lst[i]
-            if reg in self.already_used:
-                # move this reg to the end, and decrement N
-                N -= 1
-                assert N >= i
-                lst[N], lst[i] = lst[i], lst[N]
-            else:
-                i += 1
-
-    def select_call_release_gil_mode(self):
-        CallBuilderX86.select_call_release_gil_mode(self)
-        # We have to copy the arguments around a bit more in this mode,
-        # but on the other hand we don't need prepare_arguments() moving
-        # them in precisely the final registers.  Here we look around for
-        # unused registers that may be more likely usable.
-        from rpython.jit.backend.x86.regalloc import X86_64_RegisterManager
-        from rpython.jit.backend.x86.regalloc import X86_64_XMMRegisterManager
-        self.already_used = {}
-        for loc in self.arglocs:
-            self.already_used[loc] = None
-        #
-        lst = X86_64_RegisterManager.save_around_call_regs[:]
-        self._permute_to_prefer_unused_registers(lst)
-        # <optimization>
-        extra = []
-        for reg in self.asm._regalloc.rm.free_regs:
-            if (reg not in self.already_used and
-                    reg in self._ALL_CALLEE_SAVE_GPR):
-                extra.append(reg)
-        self.free_callee_save_gprs = extra
-        lst = extra + lst
-        # </optimization>
-        self.ARGUMENTS_GPR = lst[:len(self.ARGUMENTS_GPR)]
-        self.DONT_MOVE_GPR = self._ALL_CALLEE_SAVE_GPR
-        #
-        lst = X86_64_XMMRegisterManager.save_around_call_regs[:]
-        self._permute_to_prefer_unused_registers(lst)
-        self.ARGUMENTS_XMM = lst[:len(self.ARGUMENTS_XMM)]
-
     def prepare_arguments(self):
         src_locs = []
         dst_locs = []
@@ -444,78 +422,44 @@
         assert 0     # should not occur on 64-bit
 
     def load_result(self):
-        if self.restype == 'S' and self.tmpresloc is None:
+        if self.restype == 'S':
             # singlefloat return: use MOVD to load the target register
             # from the lower 32 bits of XMM0
             self.mc.MOVD32(self.resloc, xmm0)
         else:
             CallBuilderX86.load_result(self)
 
-    def save_result_value(self):
+    def save_result_value_reacq(self):
         # Temporarily save the result value into [ESP].
         if self.ressize == 0:      # void return
             return
         #
         if self.restype == FLOAT:    # and not 'S'
             self.mc.MOVSD_sx(0, xmm0.value)
-            self.tmpresloc = RawEspLoc(0, FLOAT)
             return
         #
-        if len(self.free_callee_save_gprs) == 0:
-            self.tmpresloc = RawEspLoc(0, INT)
-        else:
-            self.tmpresloc = self.free_callee_save_gprs[0]
-        #
         if self.restype == 'S':
             # singlefloat return: use MOVD to store the lower 32 bits
-            # of XMM0 into the tmpresloc (register or [ESP])
-            self.mc.MOVD32(self.tmpresloc, xmm0)
+            # of XMM0 into [ESP]
+            self.mc.MOVD32_sx(0, xmm0.value)
         else:
             assert self.restype == INT
-            self.mc.MOV(self.tmpresloc, eax)
+            self.mc.MOV_sr(0, eax.value)
 
-    def save_register_arguments(self):
-        # Save the argument registers, which are given by self.ARGUMENTS_xxx.
-        n_gpr = min(self.next_arg_gpr, len(self.ARGUMENTS_GPR))
-        n_xmm = min(self.next_arg_xmm, len(self.ARGUMENTS_XMM))
-        n_saved_regs = n_gpr + n_xmm
-        for i in range(n_gpr):
-            if self.ARGUMENTS_GPR[i] in self._ALL_CALLEE_SAVE_GPR:
-                n_saved_regs -= 1     # don't need to save it
-        self.subtract_esp_aligned(n_saved_regs)
+    def restore_result_value_reacq(self):
+        # Opposite of save_result_value_reacq()
+        if self.ressize == 0:      # void return
+            return
         #
-        n = 0
-        for i in range(n_gpr):
-            if self.ARGUMENTS_GPR[i] not in self._ALL_CALLEE_SAVE_GPR:
-                self.mc.MOV_sr(n * WORD, self.ARGUMENTS_GPR[i].value)
-                n += 1
-        for i in range(n_xmm):
-            self.mc.MOVSD_sx(n * WORD, self.ARGUMENTS_XMM[i].value)
-            n += 1
-        assert n == n_saved_regs
-        self.n_saved_regs = n_saved_regs
-
-    def restore_register_arguments(self):
-        # Restore the saved values into the *real* registers used for calls
-        # --- which are not self.ARGUMENTS_xxx!
-        n_gpr = min(self.next_arg_gpr, len(self.ARGUMENTS_GPR))
-        n_xmm = min(self.next_arg_xmm, len(self.ARGUMENTS_XMM))
+        if self.restype == FLOAT:    # and not 'S'
+            self.mc.MOVSD_xs(xmm0.value, 0)
+            return
         #
-        n = 0
-        for i in range(n_gpr):
-            tgtvalue = CallBuilder64.ARGUMENTS_GPR[i].value
-            if self.ARGUMENTS_GPR[i] not in self._ALL_CALLEE_SAVE_GPR:
-                self.mc.MOV_rs(tgtvalue, n * WORD)
-                n += 1
-            else:
-                self.mc.MOV_rr(tgtvalue, self.ARGUMENTS_GPR[i].value)
-        for i in range(n_xmm):
-            self.mc.MOVSD_xs(CallBuilder64.ARGUMENTS_XMM[i].value, n * WORD)
-            n += 1
-        assert n == self.n_saved_regs
-        #
-        if isinstance(self.fnloc, RegLoc):    # fix this register
-            self.fnloc = CallBuilder64.ARGUMENTS_GPR[n_gpr - 1]
+        if self.restype == 'S':
+            self.mc.MOVD32_xs(xmm0.value, 0)
+        else:
+            assert self.restype == INT
+            self.mc.MOV_rs(eax.value, 0)
 
 
 if IS_X86_32:
diff --git a/rpython/jit/backend/x86/rx86.py b/rpython/jit/backend/x86/rx86.py
--- a/rpython/jit/backend/x86/rx86.py
+++ b/rpython/jit/backend/x86/rx86.py
@@ -561,7 +561,7 @@
     # XXX: Only here for testing purposes..."as" happens the encode the
     # registers in the opposite order that we would otherwise do in a
     # register-register exchange.
-    #XCHG_rr = insn(rex_w, '\x87', register(1), register(2,8), '\xC0')
+    XCHG_rr = insn(rex_w, '\x87', register(1), register(2,8), '\xC0')
 
     JMP_l = insn('\xE9', relative(1))
     JMP_r = insn(rex_nw, '\xFF', orbyte(4<<3), register(1), '\xC0')
@@ -589,6 +589,8 @@
     FSTPL_b = insn('\xDD', orbyte(3<<3), stack_bp(1)) # rffi.DOUBLE ('as' wants L??)
     FSTPL_s = insn('\xDD', orbyte(3<<3), stack_sp(1)) # rffi.DOUBLE ('as' wants L??)
     FSTPS_s = insn('\xD9', orbyte(3<<3), stack_sp(1)) # lltype.SingleFloat
+    FLDL_s  = insn('\xDD', orbyte(0<<3), stack_sp(1))
+    FLDS_s  = insn('\xD9', orbyte(0<<3), stack_sp(1))
 
     # ------------------------------ Random mess -----------------------
     RDTSC = insn('\x0F\x31')
@@ -626,8 +628,10 @@
     MOVDQ_xb = xmminsn('\x66', rex_w, '\x0F\x6E', register(1, 8), stack_bp(2))
 
     MOVD32_rx = xmminsn('\x66', rex_nw, '\x0F\x7E', register(2, 8), register(1), '\xC0')
+    MOVD32_sx = xmminsn('\x66', rex_nw, '\x0F\x7E', register(2, 8), stack_sp(1))
     MOVD32_xr = xmminsn('\x66', rex_nw, '\x0F\x6E', register(1, 8), register(2), '\xC0')
     MOVD32_xb = xmminsn('\x66', rex_nw, '\x0F\x6E', register(1, 8), stack_bp(2))
+    MOVD32_xs = xmminsn('\x66', rex_nw, '\x0F\x6E', register(1, 8), stack_sp(2))
 
     PSRAD_xi = xmminsn('\x66', rex_nw, '\x0F\x72', register(1), '\xE0', immediate(2, 'b'))
 
@@ -751,7 +755,7 @@
 
 define_modrm_modes('SQRTSD_x*', ['\xF2', rex_nw, '\x0F\x51', register(1,8)], regtype='XMM')
 
-#define_modrm_modes('XCHG_r*', [rex_w, '\x87', register(1, 8)])
+define_modrm_modes('XCHG_r*', [rex_w, '\x87', register(1, 8)])
 
 define_modrm_modes('ADDSD_x*', ['\xF2', rex_nw, '\x0F\x58', register(1, 8)], regtype='XMM')
 define_modrm_modes('ADDPD_x*', ['\x66', rex_nw, '\x0F\x58', register(1, 8)], regtype='XMM')
diff --git a/rpython/jit/backend/x86/test/test_callbuilder.py b/rpython/jit/backend/x86/test/test_callbuilder.py
--- a/rpython/jit/backend/x86/test/test_callbuilder.py
+++ b/rpython/jit/backend/x86/test/test_callbuilder.py
@@ -18,16 +18,14 @@
         self._log.append(('mov', src, dst))
 
 
-def test_base_case():
+def test_base_case(call_release_gil_mode=False):
     asm = FakeAssembler()
     cb = callbuilder.CallBuilder64(asm, ImmedLoc(12345), [ebx, ebx])
+    if call_release_gil_mode:
+        cb.select_call_release_gil_mode()
     cb.prepare_arguments()
     assert asm._log == [('mov', ebx, edi),
                         ('mov', ebx, esi)]
 
-def test_bug_call_release_gil():
-    asm = FakeAssembler()
-    cb = callbuilder.CallBuilder64(asm, ImmedLoc(12345), [ebx, ebx])
-    cb.select_call_release_gil_mode()
-    cb.prepare_arguments()
-    assert asm._log == [('mov', ebx, ecx)]
+def test_call_release_gil():
+    test_base_case(call_release_gil_mode=True)
diff --git a/rpython/jit/backend/x86/test/test_rx86_32_auto_encoding.py b/rpython/jit/backend/x86/test/test_rx86_32_auto_encoding.py
--- a/rpython/jit/backend/x86/test/test_rx86_32_auto_encoding.py
+++ b/rpython/jit/backend/x86/test/test_rx86_32_auto_encoding.py
@@ -194,7 +194,8 @@
         for args in args_lists:
             suffix = ""
             if (argmodes and not self.is_xmm_insn
-                         and not instrname.startswith('FSTP')):
+                         and not instrname.startswith('FSTP')
+                         and not instrname.startswith('FLD')):
                 suffix = suffixes[self.WORD]
             # Special case: On 64-bit CPUs, rx86 assumes 64-bit integer
             # operands when converting to/from floating point, so we need to
diff --git a/rpython/memory/gctransform/asmgcroot.py b/rpython/memory/gctransform/asmgcroot.py
--- a/rpython/memory/gctransform/asmgcroot.py
+++ b/rpython/memory/gctransform/asmgcroot.py
@@ -2,6 +2,7 @@
      copygraph, SpaceOperation, checkgraph)
 from rpython.rlib.debug import ll_assert
 from rpython.rlib.nonconst import NonConstant
+from rpython.rlib import rgil
 from rpython.rtyper.annlowlevel import llhelper
 from rpython.rtyper.lltypesystem import lltype, llmemory, rffi
 from rpython.rtyper.lltypesystem.lloperation import llop
@@ -356,16 +357,19 @@
         initialframedata = anchor.address[1]
         stackscount = 0
         while initialframedata != anchor:     # while we have not looped back
-            self.fill_initial_frame(curframe, initialframedata)
-            # Loop over all the frames in the stack
-            while self.walk_to_parent_frame(curframe, otherframe):
-                swap = curframe
-                curframe = otherframe    # caller becomes callee
-                otherframe = swap
+            self.walk_frames(curframe, otherframe, initialframedata)
             # Then proceed to the next piece of stack
             initialframedata = initialframedata.address[1]
             stackscount += 1
         #
+        # for the JIT: rpy_fastgil may contain an extra framedata
+        rpy_fastgil = rgil.gil_fetch_fastgil().signed[0]
+        if rpy_fastgil != 1:
+            ll_assert(rpy_fastgil != 0, "walk_stack_from doesn't have the GIL")
+            initialframedata = rffi.cast(llmemory.Address, rpy_fastgil)
+            self.walk_frames(curframe, otherframe, initialframedata)
+            stackscount += 1
+        #
         expected = rffi.stackcounter.stacks_counter
         if NonConstant(0):
             rffi.stackcounter.stacks_counter += 42    # hack to force it
@@ -374,6 +378,14 @@
         lltype.free(otherframe, flavor='raw')
         lltype.free(curframe, flavor='raw')
 
+    def walk_frames(self, curframe, otherframe, initialframedata):
+        self.fill_initial_frame(curframe, initialframedata)
+        # Loop over all the frames in the stack
+        while self.walk_to_parent_frame(curframe, otherframe):
+            swap = curframe
+            curframe = otherframe    # caller becomes callee
+            otherframe = swap
+
     def fill_initial_frame(self, curframe, initialframedata):
         # Read the information provided by initialframedata
         initialframedata += 2*sizeofaddr #skip the prev/next words at the start
@@ -770,7 +782,7 @@
 gcrootanchor.next = gcrootanchor
 c_gcrootanchor = Constant(gcrootanchor, ASM_FRAMEDATA_HEAD_PTR)
 
-eci = ExternalCompilationInfo(pre_include_bits=['#define PYPY_USE_ASMGCC'])
+eci = ExternalCompilationInfo(compile_extra=['-DPYPY_USE_ASMGCC'])
 
 pypy_asm_stackwalk = rffi.llexternal('pypy_asm_stackwalk',
                                      [ASM_CALLBACK_PTR,
diff --git a/rpython/rlib/rgil.py b/rpython/rlib/rgil.py
new file mode 100644
--- /dev/null
+++ b/rpython/rlib/rgil.py
@@ -0,0 +1,38 @@
+import py
+from rpython.conftest import cdir
+from rpython.translator.tool.cbuild import ExternalCompilationInfo
+from rpython.rtyper.lltypesystem import lltype, llmemory, rffi
+
+# these functions manipulate directly the GIL, whose definition does not
+# escape the C code itself
+translator_c_dir = py.path.local(cdir)
+
+eci = ExternalCompilationInfo(
+    includes = ['src/thread.h'],
+    separate_module_files = [translator_c_dir / 'src' / 'thread.c'],
+    include_dirs = [translator_c_dir],
+    export_symbols = ['RPyGilAllocate', 'RPyGilYieldThread', 'RPyGilRelease',
+                      'RPyGilAcquire', 'RPyFetchFastGil'])
+
+llexternal = rffi.llexternal
+
+
+gil_allocate      = llexternal('RPyGilAllocate', [], lltype.Void,
+                               _nowrapper=True, sandboxsafe=True,
+                               compilation_info=eci)
+
+gil_yield_thread  = llexternal('RPyGilYieldThread', [], lltype.Signed,
+                               _nowrapper=True, sandboxsafe=True,
+                               compilation_info=eci)
+
+gil_release       = llexternal('RPyGilRelease', [], lltype.Void,
+                               _nowrapper=True, sandboxsafe=True,
+                               compilation_info=eci)
+
+gil_acquire       = llexternal('RPyGilAcquire', [], lltype.Void,
+                              _nowrapper=True, sandboxsafe=True,
+                              compilation_info=eci)
+
+gil_fetch_fastgil = llexternal('RPyFetchFastGil', [], llmemory.Address,
+                               _nowrapper=True, sandboxsafe=True,
+                               compilation_info=eci)
diff --git a/rpython/rlib/rthread.py b/rpython/rlib/rthread.py
--- a/rpython/rlib/rthread.py
+++ b/rpython/rlib/rthread.py
@@ -19,8 +19,7 @@
     include_dirs = [translator_c_dir],
     export_symbols = ['RPyThreadGetIdent', 'RPyThreadLockInit',
                       'RPyThreadAcquireLock', 'RPyThreadAcquireLockTimed',
-                      'RPyThreadReleaseLock', 'RPyGilAllocate',
-                      'RPyGilYieldThread', 'RPyGilRelease', 'RPyGilAcquire',
+                      'RPyThreadReleaseLock',
                       'RPyThreadGetStackSize', 'RPyThreadSetStackSize',
                       'RPyOpaqueDealloc_ThreadLock',
                       'RPyThreadAfterFork']
@@ -76,16 +75,6 @@
                                          [TLOCKP], lltype.Void,
                                          _nowrapper=True)
 
-# these functions manipulate directly the GIL, whose definition does not
-# escape the C code itself
-gil_allocate     = llexternal('RPyGilAllocate', [], lltype.Signed,
-                              _nowrapper=True)
-gil_yield_thread = llexternal('RPyGilYieldThread', [], lltype.Signed,
-                              _nowrapper=True)
-gil_release      = llexternal('RPyGilRelease', [], lltype.Void,
-                              _nowrapper=True)
-gil_acquire      = llexternal('RPyGilAcquire', [], lltype.Void,
-                              _nowrapper=True)
 
 def allocate_lock():
     return Lock(allocate_ll_lock())
diff --git a/rpython/translator/c/gcc/trackgcroot.py b/rpython/translator/c/gcc/trackgcroot.py
--- a/rpython/translator/c/gcc/trackgcroot.py
+++ b/rpython/translator/c/gcc/trackgcroot.py
@@ -858,13 +858,17 @@
         return []
 
     def _visit_xchg(self, line):
-        # only support the format used in VALGRIND_DISCARD_TRANSLATIONS
+        # support the format used in VALGRIND_DISCARD_TRANSLATIONS
         # which is to use a marker no-op "xchgl %ebx, %ebx"
         match = self.r_binaryinsn.match(line)
         source = match.group("source")
         target = match.group("target")
         if source == target:
             return []
+        # ignore the 'rpy_fastgil' atomic exchange, or any locked
+        # atomic exchange at all (involving memory)
+        if not source.startswith('%'):
+            return []
         raise UnrecognizedOperation(line)
 
     def visit_call(self, line):
diff --git a/rpython/translator/c/src/mem.c b/rpython/translator/c/src/mem.c
--- a/rpython/translator/c/src/mem.c
+++ b/rpython/translator/c/src/mem.c
@@ -115,6 +115,11 @@
         got += 1;
         fd = ((void* *) (((char *)fd) + sizeof(void*)))[0];
     }
+    if (rpy_fastgil != 1) {
+        RPyAssert(rpy_fastgil != 0,
+                          "pypy_check_stack_count doesn't have the GIL");
+        got++;  /* <= the extra one currently stored in rpy_fastgil */
+    }
     RPyAssert(got == stacks_counter - 1,
               "bad stacks_counter or non-closed stacks around");
 # endif
diff --git a/rpython/translator/c/src/thread.c b/rpython/translator/c/src/thread.c
--- a/rpython/translator/c/src/thread.c
+++ b/rpython/translator/c/src/thread.c
@@ -9,9 +9,14 @@
 #include "common_header.h"
 #endif
 
+#ifdef PYPY_USE_ASMGCC
+# include "common_header.h"
+# include "structdef.h"
+# include "forwarddecl.h"
+#endif
+
 #ifdef _WIN32
 #include "src/thread_nt.c"
 #else
 #include "src/thread_pthread.c"
 #endif
-
diff --git a/rpython/translator/c/src/thread.h b/rpython/translator/c/src/thread.h
--- a/rpython/translator/c/src/thread.h
+++ b/rpython/translator/c/src/thread.h
@@ -24,9 +24,26 @@
 
 #endif /* !_WIN32 */
 
-long RPyGilAllocate(void);
+void RPyGilAllocate(void);
 long RPyGilYieldThread(void);
-void RPyGilRelease(void);
 void RPyGilAcquire(void);
+#define RPyGilRelease _RPyGilRelease
+#define RPyFetchFastGil _RPyFetchFastGil
+
+#ifdef PYPY_USE_ASMGCC
+# define RPY_FASTGIL_LOCKED(x)   (x == 1)
+#else
+# define RPY_FASTGIL_LOCKED(x)   (x != 0)
+#endif
+
+extern long rpy_fastgil;
+
+static inline void _RPyGilRelease(void) {
+    assert(RPY_FASTGIL_LOCKED(rpy_fastgil));
+    rpy_fastgil = 0;
+}
+static inline long *_RPyFetchFastGil(void) {
+    return &rpy_fastgil;
+}
 
 #endif
diff --git a/rpython/translator/c/src/thread_gil.c b/rpython/translator/c/src/thread_gil.c
new file mode 100644
--- /dev/null
+++ b/rpython/translator/c/src/thread_gil.c
@@ -0,0 +1,177 @@
+
+/* Idea:
+
+   - "The GIL" is a composite concept.  There are two locks, and "the
+     GIL is locked" when both are locked.
+
+   - The first lock is a simple global variable 'rpy_fastgil'.  With
+     shadowstack, we use the most portable definition: 0 means unlocked
+     and != 0 means locked.  With asmgcc, 0 means unlocked but only 1
+     means locked.  A different value means unlocked too, but the value
+     is used by the JIT to contain the stack top for stack root scanning.
+
+   - The second lock is a regular mutex.  In the fast path, it is never
+     unlocked.  Remember that "the GIL is unlocked" means that either
+     the first or the second lock is unlocked.  It should never be the
+     case that both are unlocked at the same time.
+
+   - Let's call "thread 1" the thread with the GIL.  Whenever it does an
+     external function call, it sets 'rpy_fastgil' to 0 (unlocked).
+     This is the cheapest way to release the GIL.  When it returns from
+     the function call, this thread attempts to atomically change
+     'rpy_fastgil' to 1.  In the common case where it works, thread 1
+     has got the GIL back and so continues to run.
+
+   - Say "thread 2" is eagerly waiting for thread 1 to become blocked in
+     some long-running call.  Regularly, it checks if 'rpy_fastgil' is 0
+     and tries to atomically change it to 1.  If it succeeds, it means
+     that the GIL was not previously locked.  Thread 2 has now got the GIL.
+
+   - If there are more than 2 threads, the rest is really sleeping by
+     waiting on the 'mutex_gil_stealer' held by thread 2.
+
+   - An additional mechanism is used for when thread 1 wants to
+     explicitly yield the GIL to thread 2: it does so by releasing
+     'mutex_gil' (which is otherwise not released) but keeping the
+     value of 'rpy_fastgil' to 1.
+*/
+
+long rpy_fastgil = 1;
+long rpy_waiting_threads = -42;    /* GIL not initialized */
+static mutex_t mutex_gil_stealer;
+static mutex_t mutex_gil;
+
+void RPyGilAllocate(void)
+{
+    assert(RPY_FASTGIL_LOCKED(rpy_fastgil));
+    mutex_init(&mutex_gil_stealer);
+    mutex_init(&mutex_gil);
+    mutex_lock(&mutex_gil);
+    rpy_waiting_threads = 0;
+}
+
+void RPyGilAcquire(void)
+{
+    /* Acquires the GIL.
+
+       XXX Note: this function saves and restores 'errno'. This is
+       needed for now because it may be *followed* by reading the
+       'errno', although it's kind of bogus: it should be read before
+       calling RPyGilAcquire().
+     */
+    long old_fastgil = lock_test_and_set(&rpy_fastgil, 1);
+
+    if (!RPY_FASTGIL_LOCKED(old_fastgil)) {
+        /* The fastgil was not previously locked: success.
+           'mutex_gil' should still be locked at this point.
+        */
+    }
+    else {
+        /* Otherwise, another thread is busy with the GIL. */
+        SAVE_ERRNO();
+
+        /* Register me as one of the threads that is actively waiting
+           for the GIL.  The number of such threads is found in
+           rpy_waiting_threads. */
+        assert(rpy_waiting_threads >= 0);
+        atomic_increment(&rpy_waiting_threads);
+
+        /* Enter the waiting queue from the end.  Assuming a roughly
+           first-in-first-out order, this will nicely give the threads
+           a round-robin chance.
+        */
+        mutex_lock(&mutex_gil_stealer);
+
+        /* We are now the stealer thread.  Steals! */
+        while (1) {
+            /* Sleep for one interval of time.  We may be woken up earlier
+               if 'mutex_gil' is released.
+            */
+            if (mutex_lock_timeout(&mutex_gil, 0.0001)) {   /* 0.1 ms... */
+                /* We arrive here if 'mutex_gil' was recently released
+                   and we just relocked it.
+                 */
+                old_fastgil = 0;
+                break;
+            }
+
+            /* Busy-looping here.  Try to look again if 'rpy_fastgil' is
+               released.
+            */
+            if (!RPY_FASTGIL_LOCKED(rpy_fastgil)) {
+                old_fastgil = lock_test_and_set(&rpy_fastgil, 1);
+                if (!RPY_FASTGIL_LOCKED(old_fastgil))
+                    /* yes, got a non-held value!  Now we hold it. */
+                    break;
+            }
+            /* Otherwise, loop back. */
+        }
+        atomic_decrement(&rpy_waiting_threads);
+        mutex_unlock(&mutex_gil_stealer);
+
+        RESTORE_ERRNO();
+    }
+    assert(RPY_FASTGIL_LOCKED(rpy_fastgil));
+
+#ifdef PYPY_USE_ASMGCC
+    if (old_fastgil != 0) {
+        /* this case only occurs from the JIT compiler */
+        struct pypy_ASM_FRAMEDATA_HEAD0 *new =
+            (struct pypy_ASM_FRAMEDATA_HEAD0 *)old_fastgil;
+        struct pypy_ASM_FRAMEDATA_HEAD0 *root = &pypy_g_ASM_FRAMEDATA_HEAD;
+        struct pypy_ASM_FRAMEDATA_HEAD0 *next = root->as_next;
+        new->as_next = next;
+        new->as_prev = root;
+        root->as_next = new;
+        next->as_prev = new;
+    }
+#else
+    assert(old_fastgil == 0);
+#endif
+}
+
+long RPyGilYieldThread(void)
+{
+    /* can be called even before RPyGilAllocate(), but in this case,
+       'rpy_waiting_threads' will be -42. */
+    assert(RPY_FASTGIL_LOCKED(rpy_fastgil));
+    if (rpy_waiting_threads <= 0)
+        return 0;
+
+    /* Explicitly release the 'mutex_gil'.
+     */
+    mutex_unlock(&mutex_gil);
+
+    /* Now nobody has got the GIL, because 'mutex_gil' is released (but
+       rpy_fastgil is still locked).  Call RPyGilAcquire().  It will
+       enqueue ourselves at the end of the 'mutex_gil_stealer' queue.
+       If there is no other waiting thread, it will fall through both
+       its mutex_lock() and mutex_lock_timeout() now.  But that's
+       unlikely, because we tested above that 'rpy_waiting_threads > 0'.
+     */
+    RPyGilAcquire();
+    return 1;
+}
+
+/********** for tests only **********/
+
+/* These functions are usually defined as a macros RPyXyz() in thread.h
+   which get translated into calls to _RpyXyz().  But for tests we need
+   the real functions to exists in the library as well.
+*/
+
+#undef RPyGilRelease
+void RPyGilRelease(void)
+{
+    /* Releases the GIL in order to do an external function call.
+       We assume that the common case is that the function call is
+       actually very short, and optimize accordingly.
+    */
+    _RPyGilRelease();
+}
+
+#undef RPyFetchFastGil
+long *RPyFetchFastGil(void)
+{
+    return _RPyFetchFastGil();
+}
diff --git a/rpython/translator/c/src/thread_nt.c b/rpython/translator/c/src/thread_nt.c
--- a/rpython/translator/c/src/thread_nt.c
+++ b/rpython/translator/c/src/thread_nt.c
@@ -196,50 +196,40 @@
 /* GIL code                                                 */
 /************************************************************/
 
-static volatile LONG pending_acquires = -1;
-static CRITICAL_SECTION mutex_gil;
-static HANDLE cond_gil;
+typedef HANDLE mutex_t;   /* a semaphore, on Windows */
 
-long RPyGilAllocate(void)
-{
-    pending_acquires = 0;
-    InitializeCriticalSection(&mutex_gil);
-    EnterCriticalSection(&mutex_gil);
-    cond_gil = CreateEvent (NULL, FALSE, FALSE, NULL);
-    return 1;
+static void gil_fatal(const char *msg) {
+    fprintf(stderr, "Fatal error in the GIL: %s\n", msg);
+    abort();
 }
 
-long RPyGilYieldThread(void)
-{
-    /* can be called even before RPyGilAllocate(), but in this case,
-       pending_acquires will be -1 */
-    if (pending_acquires <= 0)
-        return 0;
-    InterlockedIncrement(&pending_acquires);
-    PulseEvent(cond_gil);
-
-    /* hack: the three following lines do a pthread_cond_wait(), and
-       normally specifying a timeout of INFINITE would be fine.  But the
-       first and second operations are not done atomically, so there is a
-       (small) risk that PulseEvent misses the WaitForSingleObject().
-       In this case the process will just sleep a few milliseconds. */
-    LeaveCriticalSection(&mutex_gil);
-    WaitForSingleObject(cond_gil, 15);
-    EnterCriticalSection(&mutex_gil);
-
-    InterlockedDecrement(&pending_acquires);
-    return 1;
+static inline void mutex_init(mutex_t *mutex) {
+    *mutex = CreateSemaphore(NULL, 1, 1, NULL);
+    if (*mutex == NULL)
+        gil_fatal("CreateSemaphore failed");
 }
 
-void RPyGilRelease(void)
-{
-    LeaveCriticalSection(&mutex_gil);
-    PulseEvent(cond_gil);
+static inline void mutex_lock(mutex_t *mutex) {
+    WaitForSingleObject(*mutex, INFINITE);
 }
 
-void RPyGilAcquire(void)
+static inline void mutex_unlock(mutex_t *mutex) {
+    ReleaseSemaphore(*mutex, 1, NULL);
+}
+
+static inline int mutex_lock_timeout(mutex_t *mutex, double delay)
 {
-    InterlockedIncrement(&pending_acquires);
-    EnterCriticalSection(&mutex_gil);
-    InterlockedDecrement(&pending_acquires);
+    DWORD result = WaitForSingleObject(*mutex, (DWORD)(delay * 1000.0 + 0.999));
+    return (result != WAIT_TIMEOUT);
 }
+
+#define lock_test_and_set(ptr, value)  InterlockedExchangeAcquire(ptr, value)
+#define atomic_increment(ptr)          InterlockedIncrement(ptr)
+#define atomic_decrement(ptr)          InterlockedDecrement(ptr)
+
+#define SAVE_ERRNO()      int saved_errno = errno; \
+                          DWORD saved_lasterr = GetLastError()
+#define RESTORE_ERRNO()   errno = saved_errno; \
+                          SetLastError(saved_lasterr)
+
+#include "src/thread_gil.c"
diff --git a/rpython/translator/c/src/thread_pthread.c b/rpython/translator/c/src/thread_pthread.c
--- a/rpython/translator/c/src/thread_pthread.c
+++ b/rpython/translator/c/src/thread_pthread.c
@@ -472,29 +472,7 @@
 /* GIL code                                                 */
 /************************************************************/
 
-#ifdef __llvm__
-#  define HAS_ATOMIC_ADD
-#endif
-
-#ifdef __GNUC__
-#  if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)
-#    define HAS_ATOMIC_ADD
-#  endif
-#endif
-
-#ifdef HAS_ATOMIC_ADD
-#  define atomic_add __sync_fetch_and_add
-#else
-#  if defined(__amd64__)
-#    define atomic_add(ptr, value)  asm volatile ("lock addq %0, %1"        \
-                                 : : "ri"(value), "m"(*(ptr)) : "memory")
-#  elif defined(__i386__)
-#    define atomic_add(ptr, value)  asm volatile ("lock addl %0, %1"        \
-                                 : : "ri"(value), "m"(*(ptr)) : "memory")
-#  else
-#    error "Please use gcc >= 4.1 or write a custom 'asm' for your CPU."
-#  endif
-#endif
+#include <time.h>
 
 #define ASSERT_STATUS(call)                             \
     if (call != 0) {                                    \
@@ -502,88 +480,44 @@
         abort();                                        \
     }
 
-static void _debug_print(const char *msg)
+static inline void timespec_add(struct timespec *t, double incr)
 {
-#if 0
-    int col = (int)pthread_self();
-    col = 31 + ((col / 8) % 8);
-    fprintf(stderr, "\033[%dm%s\033[0m", col, msg);
-#endif
+    /* assumes that "incr" is not too large, less than 1 second */
+    long nsec = t->tv_nsec + (long)(incr * 1000000000.0);
+    if (nsec >= 1000000000) {
+        t->tv_sec += 1;
+        nsec -= 1000000000;
+        assert(nsec < 1000000000);
+    }
+    t->tv_nsec = nsec;
 }
 
-static volatile long pending_acquires = -1;
-static pthread_mutex_t mutex_gil;
-static pthread_cond_t cond_gil;
+typedef pthread_mutex_t mutex_t;
 
-static void assert_has_the_gil(void)
-{
-#ifdef RPY_ASSERT
-    assert(pthread_mutex_trylock(&mutex_gil) != 0);
-    assert(pending_acquires >= 0);
-#endif
+static inline void mutex_init(mutex_t *mutex) {
+    ASSERT_STATUS(pthread_mutex_init(mutex, pthread_mutexattr_default));
 }
-
-long RPyGilAllocate(void)
-{
-    int status, error = 0;
-    _debug_print("RPyGilAllocate\n");
-    pending_acquires = -1;
-
-    status = pthread_mutex_init(&mutex_gil,
-                                pthread_mutexattr_default);
-    CHECK_STATUS("pthread_mutex_init[GIL]");
-
-    status = pthread_cond_init(&cond_gil,
-                               pthread_condattr_default);
-    CHECK_STATUS("pthread_cond_init[GIL]");
-
-    if (error == 0) {
-        pending_acquires = 0;
-        RPyGilAcquire();
-    }
-    return (error == 0);
+static inline void mutex_lock(mutex_t *mutex) {
+    ASSERT_STATUS(pthread_mutex_lock(mutex));
 }
-
-long RPyGilYieldThread(void)
-{
-    /* can be called even before RPyGilAllocate(), but in this case,
-       pending_acquires will be -1 */
-#ifdef RPY_ASSERT
-    if (pending_acquires >= 0)
-        assert_has_the_gil();
-#endif
-    if (pending_acquires <= 0)
+static inline void mutex_unlock(mutex_t *mutex) {
+    ASSERT_STATUS(pthread_mutex_unlock(mutex));
+}
+static inline int mutex_lock_timeout(mutex_t *mutex, double delay) {
+    struct timespec t;
+    clock_gettime(CLOCK_REALTIME, &t);
+    timespec_add(&t, delay);
+    int error_from_timedlock = pthread_mutex_timedlock(mutex, &t);
+    if (error_from_timedlock == ETIMEDOUT)
         return 0;
-    atomic_add(&pending_acquires, 1L);
-    _debug_print("{");
-    ASSERT_STATUS(pthread_cond_signal(&cond_gil));
-    ASSERT_STATUS(pthread_cond_wait(&cond_gil, &mutex_gil));
-    _debug_print("}");
-    atomic_add(&pending_acquires, -1L);
-    assert_has_the_gil();
+    ASSERT_STATUS(error_from_timedlock);
     return 1;
 }
+#define lock_test_and_set(ptr, value)  __sync_lock_test_and_set(ptr, value)
+#define atomic_increment(ptr)          __sync_fetch_and_add(ptr, 1)
+#define atomic_decrement(ptr)          __sync_fetch_and_sub(ptr, 1)
 
-void RPyGilRelease(void)
-{
-    _debug_print("RPyGilRelease\n");
-#ifdef RPY_ASSERT
-    assert(pending_acquires >= 0);
-#endif
-    assert_has_the_gil();
-    ASSERT_STATUS(pthread_mutex_unlock(&mutex_gil));
-    ASSERT_STATUS(pthread_cond_signal(&cond_gil));
-}
+#define SAVE_ERRNO()      int saved_errno = errno
+#define RESTORE_ERRNO()   errno = saved_errno
 
-void RPyGilAcquire(void)
-{
-    _debug_print("about to RPyGilAcquire...\n");
-#ifdef RPY_ASSERT
-    assert(pending_acquires >= 0);
-#endif
-    atomic_add(&pending_acquires, 1L);
-    ASSERT_STATUS(pthread_mutex_lock(&mutex_gil));
-    atomic_add(&pending_acquires, -1L);
-    assert_has_the_gil();
-    _debug_print("RPyGilAcquire\n");
-}
+#include "src/thread_gil.c"


More information about the pypy-commit mailing list