[pypy-commit] pypy int-test-is-zero: Generate more compact code for bit tests using the TEST instruction:

arigo pypy.commits at gmail.com
Sat Nov 16 06:52:27 EST 2019


Author: Armin Rigo <arigo at tunes.org>
Branch: int-test-is-zero
Changeset: r98064:092643ba66ac
Date: 2019-11-16 12:51 +0100
http://bitbucket.org/pypy/pypy/changeset/092643ba66ac/

Log:	Generate more compact code for bit tests using the TEST instruction:

	 i1 = int_and(i0, 7); i2 = int_is_zero(i1); guard_true(i2)

	 =>

	 i2 = int_test_is_zero(i0, 7); guard_true(i2)

	The more compact version doesn't need to allocate a register, copy a
	value there, AND a constant, and then CMP the result with zero.
	Instead a single TEST instruction suffices.

diff --git a/rpython/jit/backend/test/runner_test.py b/rpython/jit/backend/test/runner_test.py
--- a/rpython/jit/backend/test/runner_test.py
+++ b/rpython/jit/backend/test/runner_test.py
@@ -1491,6 +1491,8 @@
             (rop.INT_NE, lambda x, y: x != y),
             (rop.INT_GT, lambda x, y: x > y),
             (rop.INT_GE, lambda x, y: x >= y),
+            (rop.INT_TEST_IS_ZERO, lambda x, y: (x & y) == 0),
+            (rop.INT_TEST_IS_TRUE, lambda x, y: (x & y) != 0),
             ]:
             for opguard, guard_case in [
                 (rop.GUARD_FALSE, False),
diff --git a/rpython/jit/backend/test/test_random.py b/rpython/jit/backend/test/test_random.py
--- a/rpython/jit/backend/test/test_random.py
+++ b/rpython/jit/backend/test/test_random.py
@@ -546,6 +546,8 @@
             rop.UINT_LE,
             rop.UINT_GT,
             rop.UINT_GE,
+            rop.INT_TEST_IS_ZERO,
+            rop.INT_TEST_IS_TRUE,
             ]:
     OPERATIONS.append(BinaryOperation(_op, boolres=True))
 
diff --git a/rpython/jit/backend/x86/assembler.py b/rpython/jit/backend/x86/assembler.py
--- a/rpython/jit/backend/x86/assembler.py
+++ b/rpython/jit/backend/x86/assembler.py
@@ -1281,6 +1281,17 @@
                 self.flush_cc(cond, result_loc)
         return genop_cmp
 
+    def _testop(cond):
+        cond = rx86.Conditions[cond]
+        #
+        def genop_test(self, op, arglocs, result_loc):
+            if arglocs[1].is_stack() or isinstance(arglocs[0], ImmedLoc):
+                self.mc.TEST(arglocs[1], arglocs[0])
+            else:
+                self.mc.TEST(arglocs[0], arglocs[1])
+            self.flush_cc(cond, result_loc)
+        return genop_test
+
     def _if_parity_clear_zero_and_carry(self):
         jnp_location = self.mc.emit_forward_jump('NP')
         # CMP EBP, 0: as EBP cannot be null here, that operation should
@@ -1401,6 +1412,9 @@
     genop_float_gt = _cmpop_float("A", "B")
     genop_float_ge = _cmpop_float("AE","BE")
 
+    genop_int_test_is_zero = _testop("Z")
+    genop_int_test_is_true = _testop("NZ")
+
     def genop_math_sqrt(self, op, arglocs, resloc):
         self.mc.SQRTSD(arglocs[0], resloc)
 
diff --git a/rpython/jit/backend/x86/regalloc.py b/rpython/jit/backend/x86/regalloc.py
--- a/rpython/jit/backend/x86/regalloc.py
+++ b/rpython/jit/backend/x86/regalloc.py
@@ -644,6 +644,8 @@
     consider_uint_ge = _consider_compop
     consider_ptr_eq = consider_instance_ptr_eq = _consider_compop
     consider_ptr_ne = consider_instance_ptr_ne = _consider_compop
+    consider_int_test_is_zero = _consider_compop
+    consider_int_test_is_true = _consider_compop
 
     def _consider_float_op(self, op):
         loc1 = self.xrm.loc(op.getarg(1))
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
@@ -699,6 +699,9 @@
     TEST_ai = insn(rex_w, '\xF7', orbyte(0<<3), mem_reg_plus_scaled_reg_plus_const(1), immediate(2))
     TEST_mi = insn(rex_w, '\xF7', orbyte(0<<3), mem_reg_plus_const(1), immediate(2))
     TEST_ji = insn(rex_w, '\xF7', orbyte(0<<3), abs_(1), immediate(2))
+    TEST_ri = insn(rex_w, '\xF7', orbyte(0<<3), register(1), '\xC0', immediate(2))
+    TEST_bi = insn(rex_w, '\xF7', orbyte(0<<3), stack_bp(1), immediate(2))
+    TEST_br = insn(rex_w, '\x85', register(2,8), stack_bp(1))
 
     BTS_mr = insn(rex_w, '\x0F\xAB', register(2,8), mem_reg_plus_const(1))
     BTS_jr = insn(rex_w, '\x0F\xAB', register(2,8), abs_(1))
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
@@ -311,6 +311,8 @@
                 return []   # MOV AL, [immediate]: there is a special encoding
             if methname == 'MOV8_jr' and args[1] == rx86.R.al:
                 return []   # MOV [immediate], AL: there is a special encoding
+            if methname == 'TEST_ri' and args[0] == rx86.R.eax:
+                return []  # TEST EAX, constant: there is a special encoding
 
             return [args]
 
diff --git a/rpython/jit/metainterp/blackhole.py b/rpython/jit/metainterp/blackhole.py
--- a/rpython/jit/metainterp/blackhole.py
+++ b/rpython/jit/metainterp/blackhole.py
@@ -542,6 +542,12 @@
     @arguments("i", "i", returns="i")
     def bhimpl_int_signext(a, b):
         return int_signext(a, b)
+    @arguments("i", "i", returns="i")
+    def bhimpl_int_test_is_zero(a, b):
+        return (a & b) == 0
+    @arguments("i", "i", returns="i")
+    def bhimpl_int_test_is_true(a, b):
+        return (a & b) != 0
 
     @arguments("i", "i", returns="i")
     def bhimpl_uint_lt(a, b):
diff --git a/rpython/jit/metainterp/executor.py b/rpython/jit/metainterp/executor.py
--- a/rpython/jit/metainterp/executor.py
+++ b/rpython/jit/metainterp/executor.py
@@ -442,6 +442,8 @@
                          rop.GC_STORE_INDEXED,
                          rop.LOAD_FROM_GC_TABLE,
                          rop.LOAD_EFFECTIVE_ADDRESS,
+                         rop.INT_TEST_IS_ZERO,
+                         rop.INT_TEST_IS_TRUE,
                          ):      # list of opcodes never executed by pyjitpl
                 continue
             if rop._VEC_PURE_FIRST <= value <= rop._VEC_PURE_LAST:
diff --git a/rpython/jit/metainterp/optimizeopt/rewrite.py b/rpython/jit/metainterp/optimizeopt/rewrite.py
--- a/rpython/jit/metainterp/optimizeopt/rewrite.py
+++ b/rpython/jit/metainterp/optimizeopt/rewrite.py
@@ -646,6 +646,15 @@
         elif info == INFO_NULL:
             self.make_constant_int(op, not expect_nonnull)
         else:
+            box = get_box_replacement(box)
+            box1 = self.optimizer.as_operation(box)
+            if box1 is not None and box1.getopnum() == rop.INT_AND:
+                if expect_nonnull:
+                    opnum = rop.INT_TEST_IS_TRUE
+                else:
+                    opnum = rop.INT_TEST_IS_ZERO
+                args = [box1.getarg(0), box1.getarg(1)]
+                op = self.replace_op_with(op, opnum, args=args)
             return self.emit(op)
 
     def optimize_INT_IS_TRUE(self, op):
diff --git a/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py b/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py
--- a/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py
+++ b/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py
@@ -5790,3 +5790,33 @@
         i57 = int_or(i51, i52)
         """
         self.optimize_loop(ops, expected)
+
+    def test_int_test_is_zero(self):
+        ops = """
+        [i1, i2]
+        i51 = int_and(i1, i2)
+        i52 = int_is_zero(i51)
+        guard_true(i52) []
+        """
+        expected = """
+        [i1, i2]
+        i51 = int_and(i1, i2)      # likely dead instruction
+        i52 = int_test_is_zero(i1, i2)
+        guard_true(i52) []
+        """
+        self.optimize_loop(ops, expected)
+
+    def test_int_test_is_true(self):
+        ops = """
+        [i1, i2]
+        i51 = int_and(i1, i2)
+        i52 = int_is_true(i51)
+        guard_true(i52) []
+        """
+        expected = """
+        [i1, i2]
+        i51 = int_and(i1, i2)      # likely dead instruction
+        i52 = int_test_is_true(i1, i2)
+        guard_true(i52) []
+        """
+        self.optimize_loop(ops, expected)
diff --git a/rpython/jit/metainterp/resoperation.py b/rpython/jit/metainterp/resoperation.py
--- a/rpython/jit/metainterp/resoperation.py
+++ b/rpython/jit/metainterp/resoperation.py
@@ -1036,6 +1036,8 @@
     'INT_NEG/1/i',
     'INT_INVERT/1/i',
     'INT_FORCE_GE_ZERO/1/i',
+    'INT_TEST_IS_ZERO/2b/i',
+    'INT_TEST_IS_TRUE/2b/i',
     #
     'SAME_AS/1/ifr',      # gets a Const or a Box, turns it into another Box
     'CAST_PTR_TO_INT/1/i',
diff --git a/rpython/jit/metainterp/test/test_ajit.py b/rpython/jit/metainterp/test/test_ajit.py
--- a/rpython/jit/metainterp/test/test_ajit.py
+++ b/rpython/jit/metainterp/test/test_ajit.py
@@ -4832,3 +4832,11 @@
         res2 = self.interp_operations(f, [6])
         assert res1 == res2
         self.check_operations_history(guard_class=1, record_exact_class=0)
+
+    def test_int_test_instructions(self):
+        def f(x, y):
+            if (x & 7) == 0 and (y & 7) != 0:
+                return 1
+            return 0
+        res = self.interp_operations(f, [24, 25])
+        assert res == 1
diff --git a/rpython/jit/metainterp/test/test_executor.py b/rpython/jit/metainterp/test/test_executor.py
--- a/rpython/jit/metainterp/test/test_executor.py
+++ b/rpython/jit/metainterp/test/test_executor.py
@@ -187,6 +187,8 @@
         (rop.UINT_LE, lambda x, y: r_uint(x) <= r_uint(y)),
         (rop.UINT_GT, lambda x, y: r_uint(x) >  r_uint(y)),
         (rop.UINT_GE, lambda x, y: r_uint(x) >= r_uint(y)),
+        (rop.INT_TEST_IS_ZERO, lambda x, y: (x & y) == 0),
+        (rop.INT_TEST_IS_TRUE, lambda x, y: (x & y) != 0),
         ]:
         for i in range(20):
             x = pick()


More information about the pypy-commit mailing list