Lichen

optimiser.py

645:04077d4d0478
2017-03-02 Paul Boddie Added a note about incremental compilation.
     1 #!/usr/bin/env python     2      3 """     4 Optimise object layouts and generate access instruction plans.     5      6 Copyright (C) 2014, 2015, 2016, 2017 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 common import add_counter_item, get_attrname_from_location, init_item, \    23                    sorted_output, CommonOutput    24 from encoders import digest, encode_access_location, encode_instruction, get_kinds    25 from errors import OptimiseError    26 from os.path import exists, join    27 from os import makedirs    28 from referencing import decode_reference, Reference    29     30 class Optimiser(CommonOutput):    31     32     "Optimise objects in a program."    33     34     def __init__(self, importer, deducer, output):    35     36         """    37         Initialise an instance using the given 'importer' and 'deducer' that    38         will perform the arrangement of attributes for program objects, writing    39         the results to the given 'output' directory.    40         """    41     42         self.importer = importer    43         self.deducer = deducer    44         self.output = output    45     46         # Detection of differences between any existing structure or signature    47         # information and the generated information.    48     49         self.differing_structures = []    50         self.differing_parameters = []    51     52         # Locations/offsets of attributes in objects.    53     54         self.locations = None    55         self.existing_locations = None    56     57         self.attr_locations = None    58     59         # Attribute code assignments.    60     61         self.all_attrnames = None    62         self.existing_attrnames = None    63     64         # Locations of parameters in parameter tables.    65     66         self.arg_locations = None    67         self.existing_arg_locations = None    68     69         self.param_locations = None    70     71         # Parameter code assignments.    72     73         self.all_paramnames = None    74         self.existing_paramnames = None    75     76         # Specific attribute access information.    77     78         self.access_instructions = {}    79         self.accessor_kinds = {}    80     81         # Object structure information.    82     83         self.structures = {}    84         self.existing_structures = None    85         self.attr_table = {}    86     87         # Parameter list information.    88     89         self.parameters = {}    90         self.existing_parameters = None    91         self.param_table = {}    92     93         # Constant literal information.    94     95         self.constants = []    96         self.constant_numbers = {}    97         self.digests = {}    98     99         # Optimiser activities.   100    101         self.from_output()   102    103         # Define or redefine structure information.   104    105         self.populate_objects()   106         self.position_attributes()   107         self.populate_parameters()   108         self.position_parameters()   109         self.populate_tables()   110         self.populate_constants()   111         self.initialise_access_instructions()   112    113     def need_reset(self):   114    115         """   116         Return whether attribute or parameter information has changed, requiring   117         the reset/recompilation of all source files.   118         """   119    120         return self.differing_structures or self.differing_parameters   121    122     def from_output(self):   123    124         "Read input files that influence optimisation."   125    126         # Remove any output for a different program.   127    128         self.check_output()   129    130         # Existing attribute and parameter positioning information.   131    132         self.existing_locations = self.read_locations("locations", self._line_to_list, list)   133         self.existing_arg_locations = self.read_locations("parameter_locations", self._line_to_list, list)   134    135         # Existing attribute and parameter code information.   136    137         self.existing_attrnames = self.read_locations("attrnames", lambda x: x, lambda x: None)   138         self.existing_paramnames = self.read_locations("paramnames", lambda x: x, lambda x: None)   139    140         # Existing structure and signature information.   141    142         self.existing_structures = dict(self.read_locations("structures", self._line_to_structure_pairs, list))   143         self.existing_parameters = dict(self.read_locations("parameters", self._line_to_signature_pairs, list))   144    145     def _line_to_list(self, line):   146    147         "Convert comma-separated values in 'line' to a list of values."   148    149         return line.split(", ")   150    151     def _line_to_signature_pairs(self, line):   152    153         "Convert comma-separated values in 'line' to a list of pairs of values."   154    155         l = []   156         objpath, line = line.split(" ", 1)   157         for values in line.split(", "):   158             if values != "-":   159                 name, pos = values.split(":")   160                 l.append((name, int(pos)))   161             else:   162                 l.append(None)   163         return (objpath, l)   164    165     def _line_to_structure_pairs(self, line):   166    167         "Convert comma-separated values in 'line' to a list of pairs of values."   168    169         l = []   170         ref, line = line.split(" ", 1)   171         values = map(lambda x: x != '-' and x or None, line.split(", "))   172         return (decode_reference(ref), values)   173    174     def read_locations(self, filename, decode, empty):   175    176         """   177         Read location details from 'filename', using 'decode' to convert each   178         line and 'empty' to produce an empty result where no data is given on a   179         line, returning a collection.   180         """   181    182         filename = join(self.output, filename)   183         collection = []   184    185         if exists(filename):   186             f = open(filename)   187             try:   188                 for line in f.readlines():   189                     line = line.rstrip()   190                     if line:   191                         attrnames = decode(line)   192                     else:   193                         attrnames = empty()   194    195                     collection.append(attrnames)   196             finally:   197                 f.close()   198    199         return collection   200    201     def to_output(self):   202    203         "Write the output files using optimisation information."   204    205         if not exists(self.output):   206             makedirs(self.output)   207    208         self.write_objects()   209    210     def write_objects(self):   211    212         """   213         Write object-related output.   214    215         The locations are a list of positions indicating the attributes residing   216         at each position in the different structures in a program.   217    218         ----   219    220         The parameter locations are a list of positions indicating the parameters   221         residing at each position in the different parameter lists in a program.   222    223         ----   224    225         Each attribute plan provides attribute details in the following format:   226    227         location " " name " " test " " test type " " base   228                  " " traversed attributes " " traversed attribute ambiguity   229                  " " traversal access modes   230                  " " attributes to traverse " " attribute ambiguity   231                  " " context " " access method " " static attribute   232    233         Locations have the following format:   234    235         qualified name of scope "." local name ":" name version   236    237         Traversal access modes are either "class" (obtain accessor class to   238         access attribute) or "object" (obtain attribute directly from accessor).   239    240         ----   241    242         The structures are presented as a table in the following format:   243    244         qualified name " " attribute names   245    246         The attribute names are separated by ", " characters and indicate the   247         attribute provided at each position in the structure associated with the   248         given type. Where no attribute is provided at a particular location   249         within a structure, "-" is given.   250    251         ----   252    253         The parameters are presented as a table in the following format:   254    255         qualified name " " parameter details   256    257         The parameter details are separated by ", " characters and indicate   258         the parameter name and list position for each parameter described at   259         each location in the parameter table associated with the given   260         function. Where no parameter details are provided at a particular   261         location within a parameter table, "-" is given. The name and list   262         position are separated by a colon (":").   263    264         ----   265    266         The attribute table is presented as a table in the following format:   267    268         qualified name " " attribute identifiers   269    270         Instead of attribute names, identifiers defined according to the order   271         given in the "attrnames" file are employed to denote the attributes   272         featured in each type's structure. Where no attribute is provided at a   273         particular location within a structure, "-" is given.   274    275         ----   276    277         The parameter table is presented as a table in the following format:   278    279         qualified name " " parameter details   280    281         Instead of parameter names, identifiers defined according to the order   282         given in the "paramnames" file are employed to denote the parameters   283         featured in each function's parameter table. Where no parameter is   284         provided at a particular location within a table, "-" is given.   285    286         ----   287    288         The ordered list of attribute names is given in the "attrnames" file.   289    290         ----   291    292         The ordered list of parameter names is given in the "paramnames" file.   293    294         ----   295    296         The ordered list of constant literals is given in the "constants" file.   297         """   298    299         f = open(join(self.output, "locations"), "w")   300         try:   301             for attrnames in self.locations:   302                 print >>f, sorted_output(attrnames)   303    304         finally:   305             f.close()   306    307         f = open(join(self.output, "parameter_locations"), "w")   308         try:   309             for argnames in self.arg_locations:   310                 print >>f, sorted_output(argnames)   311    312         finally:   313             f.close()   314    315         f = open(join(self.output, "instruction_plans"), "w")   316         try:   317             access_instructions = self.access_instructions.items()   318             access_instructions.sort()   319    320             for location, instructions in access_instructions:   321                 print >>f, encode_access_location(location), "..."   322                 for instruction in instructions:   323                     print >>f, encode_instruction(instruction)   324                 print >>f   325    326         finally:   327             f.close()   328    329         f = open(join(self.output, "structures"), "w")   330         try:   331             structures = self.structures.items()   332             structures.sort()   333    334             for name, attrnames in structures:   335                 print >>f, name, ", ".join([s or "-" for s in attrnames])   336    337         finally:   338             f.close()   339    340         f = open(join(self.output, "parameters"), "w")   341         try:   342             parameters = self.parameters.items()   343             parameters.sort()   344    345             for name, argnames in parameters:   346                 l = []   347                 for s in argnames:   348                     l.append(s and ("%s:%d" % s) or "-")   349                 print >>f, name, ", ".join(l)   350    351         finally:   352             f.close()   353    354         f = open(join(self.output, "attrtable"), "w")   355         try:   356             attr_table = self.attr_table.items()   357             attr_table.sort()   358    359             for name, attrcodes in attr_table:   360                 l = []   361                 for i in attrcodes:   362                     l.append(i is not None and str(i) or "-")   363                 print >>f, name, ", ".join(l)   364    365         finally:   366             f.close()   367    368         f = open(join(self.output, "paramtable"), "w")   369         try:   370             param_table = self.param_table.items()   371             param_table.sort()   372    373             for name, paramcodes in param_table:   374                 l = []   375                 for s in paramcodes:   376                     l.append(s and ("%d:%d" % s) or "-")   377                 print >>f, name, ", ".join(l)   378    379         finally:   380             f.close()   381    382         f = open(join(self.output, "attrnames"), "w")   383         try:   384             for name in self.all_attrnames:   385                 print >>f, name   386    387         finally:   388             f.close()   389    390         f = open(join(self.output, "paramnames"), "w")   391         try:   392             for name in self.all_paramnames:   393                 print >>f, name   394    395         finally:   396             f.close()   397    398         f = open(join(self.output, "constants"), "w")   399         try:   400             constants = []   401             for (value, value_type, encoding), n in self.constants.items():   402                 constants.append((n, value_type, encoding, value))   403             constants.sort()   404             for n, value_type, encoding, value in constants:   405                 print >>f, value_type, encoding or "{}", repr(value)   406    407         finally:   408             f.close()   409    410     def populate_objects(self):   411    412         "Populate objects using attribute and usage information."   413    414         self.all_attrs = {}   415    416         # Partition attributes into separate sections so that class and instance   417         # attributes are treated separately.   418    419         for source, objkind in [   420             (self.importer.all_class_attrs, "<class>"),   421             (self.importer.all_instance_attrs, "<instance>"),   422             (self.importer.all_module_attrs, "<module>")   423             ]:   424    425             for name, attrnames in source.items():   426    427                 # Remove temporary names from structures.   428    429                 attrnames = filter(lambda x: not x.startswith("$t"), attrnames)   430                 self.all_attrs[(objkind, name)] = attrnames   431    432         self.locations = get_allocated_locations(self.all_attrs,   433             get_attributes_and_sizes, self.existing_locations)   434    435     def populate_parameters(self):   436    437         "Populate parameter tables using parameter information."   438    439         # Allocate positions from 1 onwards, ignoring the context argument.   440    441         self.arg_locations = [set()] + get_allocated_locations(   442             self.importer.function_parameters, get_parameters_and_sizes,   443             self.existing_arg_locations[1:])   444    445     def position_attributes(self):   446    447         "Position specific attribute references."   448    449         # Reverse the location mappings, producing a mapping from attribute   450         # names to positions.   451    452         attr_locations = self.attr_locations = {}   453         self._position_attributes(attr_locations, self.locations)   454    455         # Add any previously-known attribute locations. This prevents attributes   456         # from being assigned different identifying codes by preserving obsolete   457         # attribute codes.   458    459         if self.existing_locations:   460             self._position_attributes(attr_locations, self.existing_locations)   461    462         # Record the structures.   463    464         for (objkind, name), attrnames in self.all_attrs.items():   465             key = Reference(objkind, name)   466             l = self.structures[key] = [None] * len(attrnames)   467    468             for attrname in attrnames:   469                 position = attr_locations[attrname]   470                 if position >= len(l):   471                     l.extend([None] * (position - len(l) + 1))   472                 l[position] = attrname   473    474             # Test the structure against any existing attributes.   475    476             if self.existing_structures:   477                 if self.existing_structures.has_key(key) and self.existing_structures[key] != l:   478                     self.differing_structures.append(key)   479    480     def _position_attributes(self, d, l):   481    482         """   483         For each attribute, store a mapping in 'd' to the index in 'l' at which   484         it can be found.   485         """   486    487         for i, attrnames in enumerate(l):   488             for attrname in attrnames:   489                 if not d.has_key(attrname):   490                     d[attrname] = i   491    492     def initialise_access_instructions(self):   493    494         "Expand access plans into instruction sequences."   495    496         for access_location, access_plan in self.deducer.access_plans.items():   497    498             # Obtain the access details.   499    500             name, test, test_type, base, \   501                 traversed, traversal_modes, attrnames, \   502                 context, context_test, \   503                 first_method, final_method, \   504                 origin, accessor_kinds = access_plan   505    506             # Emit instructions by appending them to a list.   507    508             instructions = []   509             emit = instructions.append   510    511             # Identify any static original accessor.   512    513             if base:   514                 original_accessor = base   515    516             # Employ names as contexts unless the context needs testing and   517             # potentially updating. In such cases, temporary context storage is   518             # used instead.   519    520             elif name and not (context_test == "test" and   521                                final_method in ("access-invoke", "static-invoke")):   522                 original_accessor = "<name>" # refers to the name   523    524             # Use a generic placeholder representing the access expression in   525             # the general case.   526    527             else:   528                 original_accessor = "<expr>"   529    530             # Prepare for any first attribute access.   531    532             if traversed:   533                 attrname = traversed[0]   534                 del traversed[0]   535             elif attrnames:   536                 attrname = attrnames[0]   537                 del attrnames[0]   538    539             # Perform the first access explicitly if at least one operation   540             # requires it.   541    542             access_first_attribute = final_method in ("access", "access-invoke", "assign") or traversed or attrnames   543    544             # Determine whether the first access involves assignment.   545    546             assigning = not traversed and not attrnames and final_method == "assign"   547             set_accessor = assigning and "<set_target_accessor>" or "<set_accessor>"   548             stored_accessor = assigning and "<target_accessor>" or "<accessor>"   549    550             # Set the context if already available.   551    552             context_var = None   553    554             if context == "base":   555                 accessor = context_var = (base,)   556             elif context == "original-accessor":   557    558                 # Prevent re-evaluation of any dynamic expression by storing it.   559    560                 if original_accessor == "<expr>":   561                     if final_method in ("access-invoke", "static-invoke"):   562                         emit(("<set_context>", original_accessor))   563                         accessor = context_var = ("<context>",)   564                     else:   565                         emit((set_accessor, original_accessor))   566                         accessor = context_var = (stored_accessor,)   567                 else:   568                     accessor = context_var = (original_accessor,)   569    570             # Assigning does not set the context.   571    572             elif context in ("final-accessor", "unset") and access_first_attribute:   573    574                 # Prevent re-evaluation of any dynamic expression by storing it.   575    576                 if original_accessor == "<expr>":   577                     emit((set_accessor, original_accessor))   578                     accessor = (stored_accessor,)   579                 else:   580                     accessor = (original_accessor,)   581    582             # Apply any test.   583    584             if test[0] == "test":   585                 accessor = ("__%s_%s_%s" % test, accessor, test_type)   586    587             # Perform the first or final access.   588             # The access only needs performing if the resulting accessor is used.   589    590             remaining = len(traversed + attrnames)   591    592             if access_first_attribute:   593    594                 if first_method == "relative-class":   595                     if assigning:   596                         emit(("__store_via_class", accessor, attrname, "<assexpr>"))   597                     else:   598                         accessor = ("__load_via_class", accessor, attrname)   599    600                 elif first_method == "relative-object":   601                     if assigning:   602                         emit(("__store_via_object", accessor, attrname, "<assexpr>"))   603                     else:   604                         accessor = ("__load_via_object", accessor, attrname)   605    606                 elif first_method == "relative-object-class":   607                     if assigning:   608                         emit(("__get_class_and_store", accessor, attrname, "<assexpr>"))   609                     else:   610                         accessor = ("__get_class_and_load", accessor, attrname)   611    612                 elif first_method == "check-class":   613                     if assigning:   614                         emit(("__check_and_store_via_class", accessor, attrname, "<assexpr>"))   615                     else:   616                         accessor = ("__check_and_load_via_class", accessor, attrname)   617    618                 elif first_method == "check-object":   619                     if assigning:   620                         emit(("__check_and_store_via_object", accessor, attrname, "<assexpr>"))   621                     else:   622                         accessor = ("__check_and_load_via_object", accessor, attrname)   623    624                 elif first_method == "check-object-class":   625                     if assigning:   626                         emit(("__check_and_store_via_any", accessor, attrname, "<assexpr>"))   627                     else:   628                         accessor = ("__check_and_load_via_any", accessor, attrname)   629    630             # Traverse attributes using the accessor.   631    632             if traversed:   633                 for attrname, traversal_mode in zip(traversed, traversal_modes):   634                     assigning = remaining == 1 and final_method == "assign"   635    636                     # Set the context, if appropriate.   637    638                     if remaining == 1 and final_method != "assign" and context == "final-accessor":   639    640                         # Invoked attributes employ a separate context accessed   641                         # during invocation.   642    643                         if final_method in ("access-invoke", "static-invoke"):   644                             emit(("<set_context>", accessor))   645                             accessor = context_var = "<context>"   646    647                         # A private context within the access is otherwise   648                         # retained.   649    650                         else:   651                             emit(("<set_private_context>", accessor))   652                             accessor = context_var = "<private_context>"   653    654                     # Perform the access only if not achieved directly.   655    656                     if remaining > 1 or final_method in ("access", "access-invoke", "assign"):   657    658                         if traversal_mode == "class":   659                             if assigning:   660                                 emit(("__store_via_class", accessor, attrname, "<assexpr>"))   661                             else:   662                                 accessor = ("__load_via_class", accessor, attrname)   663                         else:   664                             if assigning:   665                                 emit(("__store_via_object", accessor, attrname, "<assexpr>"))   666                             else:   667                                 accessor = ("__load_via_object", accessor, attrname)   668    669                     remaining -= 1   670    671             if attrnames:   672                 for attrname in attrnames:   673                     assigning = remaining == 1 and final_method == "assign"   674    675                     # Set the context, if appropriate.   676    677                     if remaining == 1 and final_method != "assign" and context == "final-accessor":   678    679                         # Invoked attributes employ a separate context accessed   680                         # during invocation.   681    682                         if final_method in ("access-invoke", "static-invoke"):   683                             emit(("<set_context>", accessor))   684                             accessor = context_var = "<context>"   685    686                         # A private context within the access is otherwise   687                         # retained.   688    689                         else:   690                             emit(("<set_private_context>", accessor))   691                             accessor = context_var = "<private_context>"   692    693                     # Perform the access only if not achieved directly.   694    695                     if remaining > 1 or final_method in ("access", "access-invoke", "assign"):   696    697                         if assigning:   698                             emit(("__check_and_store_via_any", accessor, attrname, "<assexpr>"))   699                         else:   700                             accessor = ("__check_and_load_via_any", accessor, attrname)   701    702                     remaining -= 1   703    704             # Define or emit the means of accessing the actual target.   705    706             # Assignments to known attributes.   707    708             if final_method == "static-assign":   709                 parent, attrname = origin.rsplit(".", 1)   710                 emit(("__store_via_object", parent, attrname, "<assexpr>"))   711    712             # Invoked attributes employ a separate context.   713    714             elif final_method in ("static", "static-invoke"):   715                 accessor = ("__load_static_ignore", origin)   716    717             # Wrap accesses in context operations.   718    719             if context_test == "test":   720    721                 # Test and combine the context with static attribute details.   722    723                 if final_method == "static":   724                     emit(("__load_static_test", context_var, origin))   725    726                 # Test the context, storing it separately if required for the   727                 # immediately invoked static attribute.   728    729                 elif final_method == "static-invoke":   730                     emit(("<test_context_static>", context_var, origin))   731    732                 # Test the context, storing it separately if required for an   733                 # immediately invoked attribute.   734    735                 elif final_method == "access-invoke":   736                     emit(("<test_context_revert>", context_var, accessor))   737    738                 # Test the context and update the attribute details if   739                 # appropriate.   740    741                 else:   742                     emit(("__test_context", context_var, accessor))   743    744             elif context_test == "replace":   745    746                 # Produce an object with updated context.   747    748                 if final_method == "static":   749                     emit(("__load_static_replace", context_var, origin))   750    751                 # Omit the context update operation where the target is static   752                 # and the context is recorded separately.   753    754                 elif final_method == "static-invoke":   755                     pass   756    757                 # If a separate context is used for an immediate invocation,   758                 # produce the attribute details unchanged.   759    760                 elif final_method == "access-invoke":   761                     emit(accessor)   762    763                 # Update the context in the attribute details.   764    765                 else:   766                     emit(("__update_context", context_var, accessor))   767    768             # Omit the accessor for assignments and for invocations of static   769             # targets.   770    771             elif final_method not in ("assign", "static-assign", "static-invoke"):   772                 emit(accessor)   773    774             # Produce an advisory instruction regarding the context.   775    776             if context_var:   777                 emit(("<context_identity>", context_var))   778    779             self.access_instructions[access_location] = instructions   780             self.accessor_kinds[access_location] = accessor_kinds   781    782     def get_ambiguity_for_attributes(self, attrnames):   783    784         """   785         Return a list of attribute position alternatives corresponding to each   786         of the given 'attrnames'.   787         """   788    789         ambiguity = []   790    791         for attrname in attrnames:   792             position = self.attr_locations[attrname]   793             ambiguity.append(len(self.locations[position]))   794    795         return ambiguity   796    797     def position_parameters(self):   798    799         "Position the parameters for each function's parameter table."   800    801         # Reverse the location mappings, producing a mapping from parameter   802         # names to positions.   803    804         param_locations = self.param_locations = {}   805         self._position_attributes(param_locations, self.arg_locations)   806    807         for name, argnames in self.importer.function_parameters.items():   808    809             # Allocate an extra context parameter in the table.   810    811             l = self.parameters[name] = [None] + [None] * len(argnames)   812    813             # Store an entry for the name along with the name's position in the   814             # parameter list.   815    816             for pos, argname in enumerate(argnames):   817    818                 # Position the argument in the table.   819    820                 position = param_locations[argname]   821                 if position >= len(l):   822                     l.extend([None] * (position - len(l) + 1))   823    824                 # Indicate an argument list position starting from 1 (after the   825                 # initial context argument).   826    827                 l[position] = (argname, pos + 1)   828    829             # Test the structure against any existing parameters.   830    831             if self.existing_parameters:   832                 if self.existing_parameters.has_key(name) and self.existing_parameters[name] != l:   833                     self.differing_parameters.append(name)   834    835     def populate_tables(self):   836    837         """   838         Assign identifiers to attributes and encode structure information using   839         these identifiers.   840         """   841    842         self.all_attrnames, d = self._get_name_mapping(self.attr_locations, self.existing_attrnames)   843    844         # Record the numbers indicating the locations of the names.   845    846         for key, attrnames in self.structures.items():   847             l = self.attr_table[key] = []   848             for attrname in attrnames:   849                 if attrname is None:   850                     l.append(None)   851                 else:   852                     l.append(d[attrname])   853    854         self.all_paramnames, d = self._get_name_mapping(self.param_locations, self.existing_paramnames)   855    856         # Record the numbers indicating the locations of the names.   857    858         for key, values in self.parameters.items():   859             l = self.param_table[key] = []   860             for value in values:   861                 if value is None:   862                     l.append(None)   863                 else:   864                     name, pos = value   865                     l.append((d[name], pos))   866    867     def _get_name_mapping(self, locations, existing=None):   868    869         """   870         Get a sorted list of names from 'locations', then map them to   871         identifying numbers. Preserve the identifiers from the 'existing' list,   872         if specified. Return the list and the mapping.   873         """   874    875         d = {}   876         l = []   877    878         i = 0   879         all_names = set(locations.keys())   880    881         # Preserve the existing identifiers, if available.   882    883         if existing:   884             for name in existing:   885                 d[name] = i   886                 l.append(name)   887                 if name in all_names:   888                     all_names.remove(name)   889                 i += 1   890    891         # Include all remaining names in order.   892    893         all_names = list(all_names)   894         all_names.sort()   895    896         for name in all_names:   897             if not d.has_key(name):   898                 d[name] = i   899                 l.append(name)   900                 i += 1   901    902         return l, d   903    904     def populate_constants(self):   905    906         """   907         Obtain a collection of distinct constant literals, building a mapping   908         from constant references to those in this collection.   909         """   910    911         # Obtain mappings from constant values to identifiers.   912    913         self.constants = {}   914    915         for path, constants in self.importer.all_constants.items():   916    917             # Record constants and obtain a number for them.   918             # Each constant is actually (value, value_type, encoding).   919    920             for constant, n in constants.items():   921                 d = digest(constant)   922                 self.constants[constant] = d   923    924                 # Make sure the digests are really distinct for different   925                 # constants.   926    927                 if self.digests.has_key(d):   928                     if self.digests[d] != constant:   929                         raise OptimiseError, "Digest %s used for distinct constants %r and %r." % (   930                                              d, self.digests[d], constant)   931                 else:   932                     self.digests[d] = constant   933    934         # Establish a mapping from local constant identifiers to consolidated   935         # constant identifiers.   936    937         self.constant_numbers = {}   938    939         for name, constant in self.importer.all_constant_values.items():   940             self.constant_numbers[name] = self.constants[constant]   941    942 def combine_rows(a, b):   943     c = []   944     for i, j in zip(a, b):   945         if i is None or j is None:   946             c.append(i or j)   947         else:   948             return None   949     return c   950    951 def get_attributes_and_sizes(d):   952    953     """   954     Get attribute and size information for the object attributes defined by 'd'   955     providing a mapping from (object kind, type name) to attribute names.   956    957     Return a matrix of attributes (each row entry consisting of column values   958     providing attribute names, with value positions corresponding to types   959     providing such attributes), a list of the type names corresponding to the   960     columns in the matrix, and a list of ranked sizes each indicating...   961    962      * a weighted size depending on the kind of object   963      * the minimum size of an object employing an attribute   964      * the number of free columns in the matrix for the attribute   965      * the attribute name itself   966     """   967    968     attrs = {}   969     sizes = {}   970     objkinds = {}   971    972     for objtype, attrnames in d.items():   973         objkind, _name = objtype   974    975         for attrname in attrnames:   976    977             # Record each type supporting the attribute.   978    979             init_item(attrs, attrname, set)   980             attrs[attrname].add(objtype)   981    982             # Maintain a record of the smallest object size supporting the given   983             # attribute.   984    985             if not sizes.has_key(attrname):   986                 sizes[attrname] = len(attrnames)   987             else:   988                 sizes[attrname] = min(sizes[attrname], len(attrnames))   989    990             # Record the object types/kinds supporting the attribute.   991    992             init_item(objkinds, attrname, set)   993             objkinds[attrname].add(objkind)   994    995     # Obtain attribute details in order of size and occupancy.   996    997     all_objtypes = d.keys()   998    999     rsizes = []  1000     for attrname, size in sizes.items():  1001         priority = "<instance>" in objkinds[attrname] and 0.5 or 1  1002         occupied = len(attrs[attrname])  1003         key = (priority * size, size, len(all_objtypes) - occupied, attrname)  1004         rsizes.append(key)  1005   1006     rsizes.sort()  1007   1008     # Make a matrix of attributes.  1009   1010     matrix = {}  1011     for attrname, objtypes in attrs.items():  1012   1013         # Traverse the object types, adding the attribute name if the object  1014         # type supports the attribute, adding None otherwise.  1015   1016         row = []  1017         for objtype in all_objtypes:  1018             if objtype in objtypes:  1019                 row.append(attrname)  1020             else:  1021                 row.append(None)  1022         matrix[attrname] = row  1023   1024     return matrix, all_objtypes, rsizes  1025   1026 def get_parameters_and_sizes(d):  1027   1028     """  1029     Return a matrix of parameters, a list of functions corresponding to columns  1030     in the matrix, and a list of ranked sizes each indicating...  1031   1032      * a weighted size depending on the kind of object  1033      * the minimum size of a parameter list employing a parameter  1034      * the number of free columns in the matrix for the parameter  1035      * the parameter name itself  1036   1037     This is a slightly simpler version of the above 'get_attributes_and_sizes'  1038     function.  1039     """  1040   1041     params = {}  1042     sizes = {}  1043   1044     for name, argnames in d.items():  1045         for argname in argnames:  1046   1047             # Record each function supporting the parameter.  1048   1049             init_item(params, argname, set)  1050             params[argname].add(name)  1051   1052             # Maintain a record of the smallest parameter list supporting the  1053             # given parameter.  1054   1055             if not sizes.has_key(argname):  1056                 sizes[argname] = len(argnames)  1057             else:  1058                 sizes[argname] = min(sizes[argname], len(argnames))  1059   1060     # Obtain attribute details in order of size and occupancy.  1061   1062     names = d.keys()  1063   1064     rsizes = []  1065     for argname, size in sizes.items():  1066         occupied = len(params[argname])  1067         key = (size, size, len(names) - occupied, argname)  1068         rsizes.append(key)  1069   1070     rsizes.sort()  1071   1072     # Make a matrix of parameters.  1073   1074     matrix = {}  1075     for argname, types in params.items():  1076         row = []  1077         for name in names:  1078             if name in types:  1079                 row.append(argname)  1080             else:  1081                 row.append(None)  1082         matrix[argname] = row  1083   1084     return matrix, names, rsizes  1085   1086 def get_allocated_locations(d, fn, existing=None):  1087   1088     """  1089     Return a list where each element corresponds to a structure location and  1090     contains a set of attribute names that may be stored at that location, given  1091     a mapping 'd' whose keys are (object kind, object name) tuples and whose  1092     values are collections of attributes.  1093     """  1094   1095     matrix, names, rsizes = fn(d)  1096     allocated = []  1097   1098     # Verify any existing allocation.  1099   1100     allocated_attrnames = set()  1101   1102     if existing:  1103         for attrnames in existing:  1104   1105             # Handle empty positions.  1106   1107             if not attrnames:  1108                 allocated.append([])  1109                 continue  1110   1111             base = None  1112   1113             for attrname in attrnames:  1114   1115                 # Skip presumably-removed attribute names.  1116   1117                 if not matrix.has_key(attrname):  1118                     continue  1119   1120                 # Handle the first attribute name.  1121   1122                 if base is None:  1123                     base = matrix[attrname]  1124                     allocated_attrnames.add(attrname)  1125                     continue  1126   1127                 # Combine existing and new attribute positioning.  1128   1129                 new = combine_rows(base, matrix[attrname])  1130   1131                 if new:  1132                     base = new  1133                     allocated_attrnames.add(attrname)  1134   1135             allocated.append(base)  1136   1137     # Try to allocate each attribute name in turn.  1138   1139     pos = 0  1140   1141     while pos < len(rsizes):  1142         weight, size, free, attrname = rsizes[pos]  1143   1144         # Ignore allocated attribute names.  1145   1146         if attrname in allocated_attrnames:  1147             pos += 1  1148             continue  1149   1150         # Obtain the object information for the attribute name.  1151   1152         base = matrix[attrname]  1153   1154         # Examine attribute names that follow in the ranking, attempting to  1155         # accumulate compatible attributes that can co-exist in the same  1156         # position within structures.  1157   1158         y = pos + 1  1159         while y < len(rsizes):  1160             _weight, _size, _free, _attrname = rsizes[y]  1161   1162             # Ignore allocated attribute names.  1163   1164             if _attrname in allocated_attrnames:  1165                 y += 1  1166                 continue  1167   1168             # Determine whether this attribute is supported by too many types  1169             # to co-exist.  1170   1171             occupied = len(names) - _free  1172             if occupied > free:  1173                 break  1174   1175             # Merge the attribute support for both this and the currently  1176             # considered attribute, testing for conflicts. Adopt the merged row  1177             # details if they do not conflict.  1178   1179             new = combine_rows(base, matrix[_attrname])  1180             if new:  1181                 del matrix[_attrname]  1182                 del rsizes[y]  1183                 base = new  1184                 free -= occupied  1185   1186             # Otherwise, look for other compatible attributes.  1187   1188             else:  1189                 y += 1  1190   1191         # Allocate the merged details at the current position.  1192   1193         allocated.append(base)  1194         pos += 1  1195   1196     return allocations_to_sets(allocated)  1197   1198 def allocations_to_sets(allocated):  1199   1200     """  1201     Return the list of attribute names from each row of the 'allocated'  1202     attributes table.  1203     """  1204   1205     locations = []  1206   1207     for attrnames in allocated:  1208         l = set()  1209   1210         # Convert populated allocations.  1211   1212         if attrnames:  1213             for attrname in attrnames:  1214                 if attrname:  1215                     l.add(attrname)  1216   1217         locations.append(l)  1218   1219     return locations  1220   1221 # vim: tabstop=4 expandtab shiftwidth=4