micropython

micropython/opt.py

268:066ed6e92f1e
2009-10-26 Paul Boddie Added experimental (and not fully correct) attribute access optimisation through attribute usage observations. Added an access method to the Table class. Added a simple help mode for the test program.
     1 #!/usr/bin/env python     2      3 """     4 Optimise code produced by the AST translator.     5      6 Copyright (C) 2007, 2008, 2009 Paul Boddie <paul@boddie.org.uk>     7      8 This program is free software; you can redistribute it and/or modify it under     9 the terms of the GNU General Public License as published by the Free Software    10 Foundation; either version 3 of the License, or (at your option) any later    11 version.    12     13 This program is distributed in the hope that it will be useful, but WITHOUT    14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS    15 FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more    16 details.    17     18 You should have received a copy of the GNU General Public License along with    19 this program.  If not, see <http://www.gnu.org/licenses/>.    20 """    21     22 from micropython.common import *    23 from micropython.data import *    24 from micropython.rsvp import *    25     26 class Optimiser:    27     28     "A code optimiser."    29     30     supported_optimisations = [    31         # Code generation optimisations:    32         "constant_storage", "constant_accessor", "known_target", "self_access",    33         "temp_storage", "load_operations", "no_operations", "unused_results",    34         "unused_handlers", "accesses_by_usage",    35         # Inspection optimisations:    36         "unused_objects"    37         ]    38     39     def __init__(self, translation, optimisations=None):    40     41         """    42         Provide for the given 'translation' an optimiser for the desired    43         'optimisations'. See the 'supported_optimisations' attribute of this    44         class for permitted values.    45         """    46     47         self.translation = translation    48         self.optimisations = set(optimisations or [])    49     50         # The current "active" instruction.    51         # As a rule, this will become the last instruction, but some    52         # control-flow operations will flush the "active" instruction.    53     54         self.active = None    55     56         # The instruction providing the active value.    57     58         self.active_value = None    59     60     def reset(self):    61     62         "Reset the optimiser, clearing the active instructions."    63     64         self.clear_active_value()    65         self.clear_active()    66     67     def set_new(self, op):    68     69         "Set the latest 'op' as the active instruction."    70     71         self.set_active(op)    72         self.set_active_value(op)    73     74     def set_active_value(self, op):    75     76         "Set the value-providing active instruction."    77     78         if isinstance(op, current_value_instructions):    79             self.active_value = op    80     81     def clear_active_value(self):    82     83         "Clear the value-providing active instruction."    84     85         self.active_value = None    86     87     def remove_active_value(self):    88     89         """    90         Remove the value-providing active instruction from the generated code,    91         if appropriate, but keep a record of the active instruction itself.    92         """    93     94         if self.active_value is self.active:    95             removed = self.active    96             self.translation.remove_op()    97             return removed    98         else:    99             return None   100    101     def set_source(self, expr):   102    103         "Set the source of the active instruction to 'expr'."   104    105         if self.active is not None:   106             self.active.source = expr   107    108     def set_active(self, op):   109    110         "Set the active instruction."   111    112         self.active = op   113    114     def clear_active(self):   115    116         "Clear the active instruction."   117    118         self.active = None   119    120     # Optimisation tests.   121    122     def should_optimise_constant_storage(self):   123         return "constant_storage" in self.optimisations   124    125     def should_optimise_constant_accessor(self):   126         return "constant_accessor" in self.optimisations   127    128     def should_optimise_known_target(self):   129         return "known_target" in self.optimisations   130    131     def should_optimise_self_access(self):   132         return "self_access" in self.optimisations   133    134     def should_optimise_temp_storage(self):   135         return "temp_storage" in self.optimisations   136    137     def should_optimise_load_operations(self):   138         return "load_operations" in self.optimisations   139    140     def should_optimise_away_no_operations(self):   141         return "no_operations" in self.optimisations   142    143     def should_optimise_unused_results(self):   144         return "unused_results" in self.optimisations   145    146     def should_optimise_unused_handlers(self):   147         return "unused_handlers" in self.optimisations   148    149     def should_optimise_accesses_by_attribute_usage(self):   150         return "accesses_by_usage" in self.optimisations   151    152     # Simple tests.   153    154     def is_constant_input(self, instruction):   155    156         "Return whether 'instruction' provides a constant input."   157    158         return isinstance(instruction, LoadAddress) and instruction.attr.assignments == 1 and \   159             isinstance(instruction.attr.get_value(), Constant) or \   160             isinstance(instruction, (LoadConst, LoadClass, LoadFunction))   161    162     def is_constant_target(self, instruction):   163    164         "Return whether 'instruction' provides a constant target."   165    166         # NOTE: Removed StoreName, since this would then demand population of   167         # NOTE: locals/frames.   168    169         return isinstance(instruction, (StoreAddress, StoreAddressContext)) and \   170             instruction.attr.assignments == 1   171    172     def is_simple_input(self, instruction):   173    174         """   175         Return whether 'instruction' provides a simple input (typically a load   176         instruction). A simple input is something which would be represented by   177         a load operation from a CPU register or special memory location.   178         """   179    180         return isinstance(instruction, (LoadConst, LoadClass, LoadFunction, LoadName, LoadTemp, LoadResult, LoadException, LoadAddress))   181    182     def is_simple_input_user(self, instruction):   183    184         """   185         Return whether 'instruction' can use simple input from the current   186         value. Such instructions would, in a low-level implementation, be able   187         to have the simple input registers as operands.   188         """   189    190         return isinstance(instruction, simple_input_user_instructions)   191    192     def is_resultant_no_operation(self, instruction):   193    194         """   195         Return whether 'instruction' merely stores its input where the input   196         originally came from.   197         """   198    199         return (   200             isinstance(instruction.input, LoadTemp) and isinstance(instruction, StoreTemp) and   201                 instruction.input.attr == instruction.attr) or (   202             isinstance(instruction.input, LoadResult) and isinstance(instruction, StoreResult)   203             )   204    205     def is_input(self, instruction):   206    207         "Return whether 'instruction' provides an input."   208    209         return isinstance(instruction, current_value_instructions)   210    211     # Convenience tests on outputs.   212    213     def have_constant_target(self):   214    215         "Return whether the active instruction provides a constant target."   216    217         return self.is_constant_target(self.active)   218    219     def have_constant_source(self):   220    221         "Return whether the active instruction has a constant source."   222    223         return self.is_constant_input(self.active.source)   224    225     # Convenience tests on inputs.   226    227     def have_constant_input(self):   228    229         "Return whether the active instruction provides a constant input."   230    231         return self.is_constant_input(self.active_value)   232    233     have_known_target = have_constant_input   234    235     def have_simple_input(self):   236    237         "Return whether the active instruction provides a simple input."   238    239         return self.is_simple_input(self.active_value)   240    241     def have_input(self):   242    243         "Return whether the active instruction provides an input."   244    245         return self.is_input(self.active_value)   246    247     def have_self_input(self, unit):   248    249         """   250         Return whether the active instruction is a reference to self in the   251         given 'unit'.   252         """   253    254         return isinstance(unit, Function) and \   255             unit.is_method() and isinstance(self.active_value, LoadName) and \   256             self.active_value.attr.name == "self"   257    258     def have_temp_compatible_access(self):   259    260         """   261         Indicate whether the active instruction can be used in place of access   262         to a temporary variable retaining the result of the last instruction.   263         """   264    265         # LoadResult cannot be relied upon in general since the result register   266         # could be updated since first being referenced.   267    268         return isinstance(self.active_value, (LoadName, LoadTemp, LoadAddress, LoadConst, LoadClass, LoadFunction)) or \   269             isinstance(self.active_value, LoadResult) and self.active_value is self.active or \   270             isinstance(self.active_value, LoadException) and self.active_value is self.active   271    272     def have_correct_self_for_target(self, context, unit):   273    274         "Return whether the 'context' is compatible with the given 'unit'."   275    276         if context is not None and self.have_self_input(unit):   277    278             parent = unit.parent   279             if parent is context or parent.has_subclass(context) or context.has_subclass(parent):   280                 return 1   281    282         return 0   283    284     def have_empty_handler(self, instruction):   285    286         """   287         Return whether the active instruction defined a handler for exceptions   288         which is then discarded by the given 'instruction'.   289         """   290    291         return isinstance(self.translation.last_op(), PushHandler) and isinstance(instruction, PopHandler)   292    293     # Optimisation methods. See the supported_optimisations class attribute.   294    295     def optimise_constant_storage(self):   296    297         """   298         Where the last operation stores a constant into a target which is also   299         constant, indicate that both operations should be optimised away.   300         """   301    302         return self.should_optimise_constant_storage() and \   303             self.have_constant_target() and \   304             self.have_constant_source()   305    306     def optimise_constant_accessor(self):   307    308         """   309         Where the object whose attribute is being accessed is constant, provide   310         information about its full name.   311         """   312    313         if self.should_optimise_constant_accessor() and self.have_constant_input():   314             value = self.active_value   315    316             # Get the details of the access.   317    318             if isinstance(value.attr, Const):   319                 target_name = value.attr.value_type_name()   320             else:   321                 target = value.attr.get_value()   322    323                 if target is None:   324                     return None # no clearly defined target   325                 elif isinstance(target, Const):   326                     return target.value_type_name()   327                 elif isinstance(target, Instance):   328                     return None # skip production of optimised code   329                 else:   330                     return target.full_name()   331    332         else:   333             return None   334    335     def optimise_known_target(self):   336    337         """   338         Where the target of an invocation is known, provide information about it   339         and its context. If a class is being invoked and the conditions are   340         appropriate, get information about the specific initialiser.   341         """   342    343         if self.should_optimise_known_target() and self.have_known_target():   344             value = self.active_value   345             target = value.attr.get_value()   346             context = value.attr.get_context()   347    348             # Return target details only if this is clearly defined.   349    350             if target is not None:   351                 return target, context   352    353         return None   354    355     def optimise_self_access(self, unit, attrname):   356    357         """   358         Return whether code in the given 'unit' is able to access the given   359         'attrname' through the same position in all possible objects which might   360         be accessed.   361         """   362    363         return self.should_optimise_self_access() and \   364             self.have_self_input(unit) and not unit.is_relocated(attrname)   365    366     def optimise_temp_storage(self):   367    368         """   369         Where the next operation would involve storing a value into temporary   370         storage at 'temp_position', record and remove any simple instruction   371         which produced the value to be stored such that instead of subsequently   372         accessing the temporary storage, that instruction is substituted.   373    374         If no optimisation can be achieved, a StoreTemp instruction is produced   375         and the appropriate LoadTemp instruction is returned.   376    377         Restriction: for use only in situations where the source of the   378         temporary data will not be disturbed between its first access and its   379         subsequent use.   380         """   381    382         if self.should_optimise_temp_storage() and \   383             self.have_temp_compatible_access():   384    385             # Remove the active value contributor.   386    387             removed = self.remove_active_value()   388    389             # Extend the lifetime of any temporary storage location.   390    391             self.translation.ensure_temp(removed)   392             return removed   393         else:   394             return self.translation.get_temp()   395    396     def optimise_load_operations(self, instruction):   397    398         """   399         Incorporate previous load operations into other operations.   400         """   401    402         if self.should_optimise_load_operations() and \   403             self.have_simple_input() and \   404             self.is_simple_input_user(instruction):   405    406             self.remove_active_value()   407             instruction.input = self.active_value   408    409     def optimise_away_no_operations(self, instruction):   410    411         """   412         Optimise away operations which just store their inputs in the place   413         the inputs originally came from.   414         """   415    416         return self.should_optimise_away_no_operations() and \   417             self.is_resultant_no_operation(instruction)   418    419     def optimise_unused_results(self):   420    421         "Discard results which will not be used."   422    423         if self.should_optimise_unused_results() and self.have_simple_input():   424             self.remove_active_value()   425    426     def optimise_unused_handlers(self, instruction):   427    428         "Discard handlers which will not be used."   429    430         if self.should_optimise_unused_handlers() and self.have_empty_handler(instruction):   431             self.translation.remove_op()   432             return 1   433         else:   434             return 0   435    436 # vim: tabstop=4 expandtab shiftwidth=4