Commits

Victor Stinner committed 1ce8726

Add a more aggressive option to remove dead code:
config.remove_almost_dead_code, disabled by default

Comments (0)

Files changed (4)

 Changes:
 
  * Add Config.enable_all_optimizations() method
+ * Add a more aggressive option to remove dead code:
+   config.remove_almost_dead_code, disabled by default
  * Unroll loops (no support for break/continue yet)
  * Remove useless instructions. Example:
    "x=1; 'abc'; print(x)" => "x=1; print(x)"

astoptimizer/config.py

         # Example: "if 0: print('debug')" => "pass"
         self.remove_dead_code = True
 
+        # Remove "almost dead" code?
+        # More aggressive removal of dead code. For example, "obj.attr" is
+        # considered as dead code, whereas is may be used to check if an
+        # attribute exists:
+        #     try:
+        #         obj.attr
+        #     except AttributeError:
+        #         pass
+        self.remove_almost_dead_code = False
+
         # Remove dead code in a module body?
         # Option used by astoptimizer unit tests
         # Example: "print('hello'); 1" => "print('hello')"
             self.enable(feature)
         self.remove_dead_code = True
         #self.use_experimental_vars = True
+        self.remove_almost_dead_code = True
 
     def _add_module(self, name):
         if '.' not in name:

astoptimizer/optimizer.py

     from astoptimizer.ast_tools import new_set, new_set_elts
 
 DROP_NODE = object()
+PYTHON33 = (sys.version_info >= (3, 3))
 PYTHON34 = (sys.version_info >= (3, 4))
 
 def operator_not_in(data, item):
         if not isinstance(node, ast.Expr):
             return False
         expr = node.value
-        if not isinstance(expr, (ast.Str, ast.Num)):
+        if PYTHON33:
+            obj_types = (ast.Call, ast.Yield, ast.YieldFrom)
+        else:
+            obj_types = (ast.Call, ast.Yield)
+        if isinstance(expr, obj_types):
             return False
+        if self.config.remove_almost_dead_code:
+            # Keep division by zero
+            if (isinstance(expr, ast.BinOp)
+            and isinstance(expr.op, (ast.Div, ast.FloorDiv))
+            and isinstance(expr.right, ast.Num)
+            and expr.right.n == 0):
+                return False
+        else:
+            if PYTHON3:
+                obj_types = (ast.Str, ast.Num, ast.Bytes)
+            else:
+                obj_types = (ast.Str, ast.Num)
+            if not isinstance(expr, obj_types):
+                return False
         if (index == 0
-        and isinstance(expr, ast.Str)
-        and not self.config.remove_docstring):
+        and not self.config.remove_docstring
+        and isinstance(expr, ast.Str)):
             return False
         return True
 

astoptimizer/tests.py

 import unittest
 import warnings
 
-from astoptimizer import Config, parse_ast, compile_ast
+from astoptimizer import Config as _Config, parse_ast, compile_ast
 from astoptimizer.compatibility import (
     PYTHON2, PYTHON27, PYTHON3,
     u, b,
         self.warnings.append(message)
 
     def create_config(self, *features):
-        config = Config(*features)
+        config = _Config(*features)
         config.info_func = self.log_info
         config.warning_func = self.log_warning
         config._remove_module_dead_code = False
         """.strip()
         self.check_not_optimized(code, catch_syntaxerror=True)
 
-        # docstring
+    def test_remove_docstring(self):
         keep_docstring = self.create_config()
         keep_docstring.remove_docstring = False
         self.check('def f():\n "docstring"\n return 1',
                    info="Remove dead code at <string>:2: Expr(value=Str(s='docstring'))",
                    config=no_docstring)
 
+    def test_remove_almost_dead_code(self):
+        config = self.create_config()
+        config.remove_almost_dead_code = True
+        config._remove_module_dead_code = True
+        self.check('obj.attr',
+                   self.text_ast('pass'),
+                   config=config,
+                   info=re.compile('^Remove dead code'))
+        self.check_not_optimized('func()', config=config)
+
     def test_dont_remove_dead_code(self):
         # disable removal of dead code
-        config = Config()
+        config = self.create_config()
         config.remove_dead_code = False
+        config._remove_module_dead_code = True
         config.unroll_limit = 0
         self.check_not_optimized('def f():\n return 1\n return 2', config)
         self.check('if False: print("log")',