Author: Carl Friedrich Bolz <[email protected]>
Branch: regalloc-playground
Changeset: r92201:a961fe5b9c4a
Date: 2017-08-21 20:55 +0200
http://bitbucket.org/pypy/pypy/changeset/a961fe5b9c4a/

Log:    implement the most common spilling heuristic used in linear scan
        implementations:

        spill the variable where the next use as as argument is the furthest
        away from the current position.

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
@@ -416,9 +416,13 @@
 
     def _pick_variable_to_spill(self, v, forbidden_vars, selected_reg=None,
                                 need_lower_byte=False):
-        """ Slightly less silly algorithm.
-        """
-        cur_max_age = -1
+        # try to spill a variable that has no further real usages, ie that only
+        # appears in failargs or in a jump
+        # if that doesn't exist, spill the variable that has a real_usage that
+        # is the furthest away from the current position
+
+        cur_max_use_distance = -1
+        position = self.position
         candidate = None
         cur_max_age_failargs = -1
         candidate_from_failargs = None
@@ -434,17 +438,19 @@
             if need_lower_byte and reg in self.no_lower_byte_regs:
                 continue
             lifetime = self.longevity[next]
-            max_age = lifetime.last_usage
-            if lifetime.is_last_real_use_before(self.position):
+            if lifetime.is_last_real_use_before(position):
                 # this variable has no "real" use as an argument to an op left
                 # it is only used in failargs, and maybe in a jump. spilling is
                 # fine
+                max_age = lifetime.last_usage
                 if cur_max_age_failargs < max_age:
                     cur_max_age_failargs = max_age
                     candidate_from_failargs = next
-            if cur_max_age < max_age:
-                cur_max_age = max_age
-                candidate = next
+            else:
+                use_distance = lifetime.next_real_usage(position) - position
+                if cur_max_use_distance < use_distance:
+                    cur_max_use_distance = use_distance
+                    candidate = next
         if candidate_from_failargs is not None:
             return candidate_from_failargs
         if candidate is not None:
@@ -809,8 +815,7 @@
 UNDEF_POS = -42
 
 class Lifetime(object):
-    def __init__(self, definition_pos=UNDEF_POS, last_usage=UNDEF_POS,
-                 last_real_usage=UNDEF_POS):
+    def __init__(self, definition_pos=UNDEF_POS, last_usage=UNDEF_POS):
         # all positions are indexes into the operations list
 
         # the position where the variable is defined
@@ -819,16 +824,38 @@
         # and jumps
         self.last_usage = last_usage
 
-        # last *real* usage, ie as an argument to an operation
-        # after last_real_usage and last_usage it does not matter whether the
-        # variable is stored on the stack
-        self.last_real_usage = last_real_usage
+        # *real* usages, ie as an argument to an operation (as opposed to jump
+        # arguments or in failargs)
+        self.real_usages = None
 
     def is_last_real_use_before(self, position):
-        return self.last_real_usage <= position
+        if self.real_usages is None:
+            return True
+        return self.real_usages[-1] <= position
+
+    def next_real_usage(self, position):
+        assert position >= self.definition_pos
+        # binary search
+        l = self.real_usages
+        low = 0
+        high = len(l)
+        while low < high:
+            mid = low + (high - low) // 2 # no overflow ;-)
+            if position < l[mid]:
+                high = mid
+            else:
+                low = mid + 1
+        return l[low]
+
+    def _check_invariants(self):
+        assert self.definition_pos <= self.last_usage
+        if self.real_usages is not None:
+            assert sorted(self.real_usages) == self.real_usages
+            assert self.last_usage >= max(self.real_usages)
+            assert self.definition_pos < min(self.real_usages)
 
     def __repr__(self):
-        return "%s:%s(%s)" % (self.definition_pos, self.last_real_usage, 
self.last_usage)
+        return "%s:%s(%s)" % (self.definition_pos, self.real_usages, 
self.last_usage)
 
 def compute_vars_longevity(inputargs, operations):
     # compute a dictionary that maps variables to Lifetime information
@@ -855,8 +882,9 @@
             else:
                 lifetime = longevity[arg]
             if opnum != rop.JUMP and opnum != rop.LABEL:
-                if lifetime.last_real_usage == UNDEF_POS:
-                    lifetime.last_real_usage = i
+                if lifetime.real_usages is None:
+                    lifetime.real_usages = []
+                lifetime.real_usages.append(i)
         if rop.is_guard(op.opnum):
             for arg in op.getfailargs():
                 if arg is None: # hole
@@ -868,7 +896,7 @@
     for arg in inputargs:
         assert not isinstance(arg, Const)
         if arg not in longevity:
-            longevity[arg] = Lifetime(-1, -1, -1)
+            longevity[arg] = Lifetime(-1, -1)
 
     if not we_are_translated():
         produced = {}
@@ -879,6 +907,11 @@
                 if not isinstance(arg, Const):
                     assert arg in produced
             produced[op] = None
+    for lifetime in longevity.itervalues():
+        if lifetime.real_usages is not None:
+            lifetime.real_usages.reverse()
+        if not we_are_translated():
+            lifetime._check_invariants()
 
     return longevity
 
diff --git a/rpython/jit/backend/llsupport/test/test_regalloc.py 
b/rpython/jit/backend/llsupport/test/test_regalloc.py
--- a/rpython/jit/backend/llsupport/test/test_regalloc.py
+++ b/rpython/jit/backend/llsupport/test/test_regalloc.py
@@ -13,10 +13,14 @@
     return [InputArgRef() for _ in range(count)]
 
 def Lifetime(definition_pos=UNDEF_POS, last_usage=UNDEF_POS,
-             last_real_usage=UNDEF_POS):
-    if last_real_usage == UNDEF_POS:
-        last_real_usage = last_usage
-    return RealLifetime(definition_pos, last_usage, last_real_usage)
+             real_usages=UNDEF_POS):
+    if real_usages == UNDEF_POS:
+        real_usages = last_usage
+    lifetime = RealLifetime(definition_pos, last_usage)
+    if isinstance(real_usages, int):
+        real_usages = [real_usages]
+    lifetime.real_usages = real_usages
+    return lifetime
 
 
 def boxes_and_longevity(num):
@@ -94,6 +98,16 @@
     def regalloc_mov(self, from_loc, to_loc):
         self.moves.append((from_loc, to_loc))
 
+
+def test_lifetime_next_real_usage():
+    lt = RealLifetime(0, 1000)
+    lt.real_usages = [0, 1, 5, 10, 24, 35, 55, 56, 57, 90, 92, 100]
+    for i in range(100):
+        next = lt.next_real_usage(i)
+        assert next in lt.real_usages
+        assert next > i
+        assert lt.real_usages[lt.real_usages.index(next) - 1] <= i
+
 class TestRegalloc(object):
     def test_freeing_vars(self):
         b0, b1, b2 = newboxes(0, 0, 0)
@@ -382,7 +396,7 @@
         xrm.loc(f0)
         rm.loc(b0)
         assert fm.get_frame_depth() == 3
-                
+
     def test_spilling(self):
         b0, b1, b2, b3, b4, b5 = newboxes(0, 1, 2, 3, 4, 5)
         longevity = {b0: Lifetime(0, 3), b1: Lifetime(0, 3),
@@ -403,6 +417,27 @@
         assert spilled2 is loc
         rm._check_invariants()
 
+    def test_spilling_furthest_next_real_use(self):
+        b0, b1, b2, b3, b4, b5 = newboxes(0, 1, 2, 3, 4, 5)
+        longevity = {b0: Lifetime(0, 3, [1, 2, 3]), b1: Lifetime(0, 3, [3]),
+                     b3: Lifetime(0, 4, [1, 2, 3, 4]), b2: Lifetime(0, 2),
+                     b4: Lifetime(1, 4), b5: Lifetime(1, 3)}
+        fm = TFrameManager()
+        asm = MockAsm()
+        rm = RegisterManager(longevity, frame_manager=fm, assembler=asm)
+        rm.next_instruction()
+        for b in b0, b1, b2, b3:
+            rm.force_allocate_reg(b)
+        assert len(rm.free_regs) == 0
+        rm.next_instruction()
+        loc = rm.loc(b1)
+        spilled = rm.force_allocate_reg(b4)
+        assert spilled is loc
+        spilled2 = rm.force_allocate_reg(b5)
+        assert spilled2 is loc
+        rm._check_invariants()
+
+
     def test_spill_useless_vars_first(self):
         b0, b1, b2, b3, b4, b5 = newboxes(0, 1, 2, 3, 4, 5)
         longevity = {b0: Lifetime(0, 5), b1: Lifetime(0, 10),
diff --git a/rpython/jit/backend/llsupport/test/test_regalloc_integration.py 
b/rpython/jit/backend/llsupport/test/test_regalloc_integration.py
--- a/rpython/jit/backend/llsupport/test/test_regalloc_integration.py
+++ b/rpython/jit/backend/llsupport/test/test_regalloc_integration.py
@@ -409,28 +409,35 @@
 
     def test_longevity(self):
         ops = """
-        [i0, i1, i2, i3, i4]
-        i5 = int_add(i0, i1)
-        i6 = int_is_true(i5)
-        guard_true(i6) [i0, i4]
-        jump(i5, i1, i2, i3, i5)
+        [i0, i1, i2, i3, i4, i10]
+        i5 = int_add(i0, i1)     # 0
+        i8 = int_add(i0, i1)     # 1 unused result, so not in real_usages
+        i6 = int_is_true(i5)     # 2
+        i11 = int_add(i5, i10)   # 3
+        guard_true(i6) [i0, i4]  # 4
+        jump(i5, i1, i2, i3, i5, i11) # 5
         """
         regalloc = self.prepare_loop(ops)
-        i0, i1, i2, i3, i4 = self.loop.inputargs
+        i0, i1, i2, i3, i4, i10 = self.loop.inputargs
         i5 = self.loop.operations[0]
+        i6 = self.loop.operations[2]
         longevity = regalloc.longevity
-        longevity[i0].last_usage == 2
-        longevity[i0].last_real_usage == 0
-        longevity[i1].last_usage == 3
-        longevity[i1].last_real_usage == 0
-        longevity[i2].last_usage == 3
-        longevity[i2].last_real_usage == 0
-        longevity[i3].last_usage == 3
-        longevity[i3].last_real_usage == 0
-        longevity[i4].last_usage == 3
-        longevity[i4].last_real_usage == -1
-        longevity[i5].last_usage == 3
-        longevity[i5].last_real_usage == 2
+        assert longevity[i0].last_usage == 4
+        assert longevity[i0].real_usages == [0]
+        assert longevity[i1].last_usage == 5
+        assert longevity[i1].real_usages == [0]
+        assert longevity[i2].last_usage == 5
+        assert longevity[i2].real_usages is None
+        assert longevity[i3].last_usage == 5
+        assert longevity[i3].real_usages is None
+        assert longevity[i4].last_usage == 4
+        assert longevity[i4].real_usages is None
+        assert longevity[i5].last_usage == 5
+        assert longevity[i5].real_usages == [2, 3]
+        assert longevity[i6].last_usage == 4
+        assert longevity[i6].real_usages == [4]
+        assert longevity[i10].last_usage == 3
+        assert longevity[i10].real_usages == [3]
 
 class TestRegallocCompOps(BaseTestRegalloc):
 
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to