[pypy-commit] pypy default: Improve the algorithm in before_call(): it now tries to emit some

arigo pypy.commits at gmail.com
Wed Jun 1 13:18:54 EDT 2016


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r84866:c65cfaeb741a
Date: 2016-06-01 19:19 +0200
http://bitbucket.org/pypy/pypy/changeset/c65cfaeb741a/

Log:	Improve the algorithm in before_call(): it now tries to emit some
	register-register moves if possible, instead of spilling to the
	jitframe.

diff --git a/rpython/jit/backend/llsupport/regalloc.py b/rpython/jit/backend/llsupport/regalloc.py
--- a/rpython/jit/backend/llsupport/regalloc.py
+++ b/rpython/jit/backend/llsupport/regalloc.py
@@ -574,27 +574,113 @@
             self.assembler.regalloc_mov(reg, to)
         # otherwise it's clean
 
+    def _bc_spill(self, v, new_free_regs):
+        self._sync_var(v)
+        new_free_regs.append(self.reg_bindings.pop(v))
+
     def before_call(self, force_store=[], save_all_regs=0):
-        """ Spill registers before a call, as described by
-        'self.save_around_call_regs'.  Registers are not spilled if
-        they don't survive past the current operation, unless they
-        are listed in 'force_store'.  'save_all_regs' can be 0 (default),
-        1 (save all), or 2 (save default+PTRs).
+        """Spill or move some registers before a call.  By default,
+        this means: for every register in 'self.save_around_call_regs',
+        if there is a variable there and it survives longer than
+        the current operation, then it is spilled/moved somewhere else.
+
+        'save_all_regs' can be 0 (default set of registers), 1 (do that
+        for all registers), or 2 (default + gc ptrs).
+
+        Overview of what we do (the implementation does it differently,
+        for the same result):
+
+        * we first check the set of registers that are free: call it F.
+
+        * possibly_free_vars() is implied for all variables (except
+          the ones listed in force_store): if they don't survive past
+          the current operation, they are forgotten now.  (Their
+          register remain not in F, because they are typically
+          arguments to the call, so they should not be overwritten by
+          the next step.)
+
+        * then for every variable that needs to be spilled/moved: if
+          there is an entry in F that is acceptable, pick it and emit a
+          move.  Otherwise, emit a spill.  Start doing this with the
+          variables that survive the shortest time, to give them a
+          better change to remain in a register---similar algo as
+          _pick_variable_to_spill().
+
+        Note: when a register is moved, it often (but not always) means
+        we could have been more clever and picked a better register in
+        the first place, when we did so earlier.  It is done this way
+        anyway, as a local hack in this function, because on x86 CPUs
+        such register-register moves are almost free.
         """
+        new_free_regs = []
+        move_or_spill = []
+
         for v, reg in self.reg_bindings.items():
-            if v not in force_store and self.longevity[v][1] <= self.position:
+            max_age = self.longevity[v][1]
+            if v not in force_store and max_age <= self.position:
                 # variable dies
                 del self.reg_bindings[v]
-                self.free_regs.append(reg)
+                new_free_regs.append(reg)
                 continue
-            if save_all_regs != 1 and reg not in self.save_around_call_regs:
-                if save_all_regs == 0:
-                    continue    # we don't have to
-                if v.type != REF:
-                    continue    # only save GC pointers
-            self._sync_var(v)
-            del self.reg_bindings[v]
-            self.free_regs.append(reg)
+
+            if save_all_regs == 1:
+                # we need to spill all registers in this mode
+                self._bc_spill(v, new_free_regs)
+                #
+            elif save_all_regs == 2 and v.type == REF:
+                # we need to spill all GC ptrs in this mode
+                self._bc_spill(v, new_free_regs)
+                #
+            elif reg not in self.save_around_call_regs:
+                continue  # in a register like ebx/rbx: it is fine where it is
+                #
+            else:
+                # this is a register like eax/rax, which needs either
+                # spilling or moving.
+                move_or_spill.append((v, max_age))
+
+        if len(move_or_spill) > 0:
+            while len(self.free_regs) > 0:
+                new_reg = self.free_regs.pop()
+                if new_reg in self.save_around_call_regs:
+                    new_free_regs.append(new_reg)    # not this register...
+                    continue
+                # This 'new_reg' is suitable for moving a candidate to.
+                # Pick the one with the smallest max_age.  (This
+                # is one step of a naive sorting algo, slow in theory,
+                # but the list should always be very small so it
+                # doesn't matter.)
+                best_i = 0
+                smallest_max_age = move_or_spill[0][1]
+                for i in range(1, len(move_or_spill)):
+                    max_age = move_or_spill[i][1]
+                    if max_age < smallest_max_age:
+                        best_i = i
+                        smallest_max_age = max_age
+                v, max_age = move_or_spill.pop(best_i)
+                # move from 'reg' to 'new_reg'
+                reg = self.reg_bindings[v]
+                if not we_are_translated():
+                    if move_or_spill:
+                        assert max_age <= min([_a for _, _a in move_or_spill])
+                    assert reg in self.save_around_call_regs
+                    assert new_reg not in self.save_around_call_regs
+                self.assembler.regalloc_mov(reg, new_reg)
+                self.reg_bindings[v] = new_reg    # change the binding
+                new_free_regs.append(reg)
+                #
+                if len(move_or_spill) == 0:
+                    break
+            else:
+                # no more free registers to move to, spill the rest
+                for v, max_age in move_or_spill:
+                    self._bc_spill(v, new_free_regs)
+
+        # re-add registers in 'new_free_regs', but in reverse order,
+        # so that the last ones (added just above, from
+        # save_around_call_regs) are picked last by future '.pop()'
+        while len(new_free_regs) > 0:
+            self.free_regs.append(new_free_regs.pop())
 
     def after_call(self, v):
         """ Adjust registers according to the result of the call,


More information about the pypy-commit mailing list