Lichen

optimiser.py

688:0ffb65419ef3
2017-03-09 Paul Boddie Merged changes from the default branch. normal-function-parameters
     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 init_item, sorted_output, CommonOutput    23 from encoders import digest    24 from errors import OptimiseError    25 from os.path import exists, join    26 from os import makedirs    27 from referencing import decode_reference, Reference    28     29 class Optimiser(CommonOutput):    30     31     "Optimise objects in a program."    32     33     def __init__(self, importer, deducer, output,    34         attrnames_filename=None, locations_filename=None,    35         paramnames_filename=None, parameter_locations_filename=None):    36     37         """    38         Initialise an instance using the given 'importer' and 'deducer' that    39         will perform the arrangement of attributes for program objects, writing    40         the results to the given 'output' directory.    41     42         If 'attrnames_filename', 'locations_filename', 'paramnames_filename', or    43         'parameter_locations_filename' are given, they will be used to    44         explicitly indicate existing attribute code, attribute position,    45         parameter code, and parameter position information respectively.    46         """    47     48         self.importer = importer    49         self.deducer = deducer    50         self.output = output    51     52         # Explicitly-specified attribute and parameter sources.    53     54         self.attrnames_filename = attrnames_filename    55         self.locations_filename = locations_filename    56         self.paramnames_filename = paramnames_filename    57         self.parameter_locations_filename = parameter_locations_filename    58     59         # Detection of differences between any existing structure or signature    60         # information and the generated information.    61     62         self.differing_structures = []    63         self.differing_parameters = []    64     65         # Locations/offsets of attributes in objects.    66     67         self.locations = None    68         self.existing_locations = None    69     70         self.attr_locations = None    71     72         # Attribute code assignments.    73     74         self.all_attrnames = None    75         self.existing_attrnames = None    76         self.indicated_attrnames = None    77     78         # Locations of parameters in parameter tables.    79     80         self.arg_locations = None    81         self.existing_arg_locations = None    82     83         self.param_locations = None    84     85         # Parameter code assignments.    86     87         self.all_paramnames = None    88         self.existing_paramnames = None    89         self.indicated_paramnames = None    90     91         # Object structure information.    92     93         self.structures = {}    94         self.existing_structures = None    95         self.attr_table = {}    96     97         # Parameter list information.    98     99         self.parameters = {}   100         self.existing_parameters = None   101         self.param_table = {}   102    103         # Constant literal information.   104    105         self.constants = []   106         self.constant_numbers = {}   107         self.digests = {}   108    109         # Optimiser activities.   110    111         self.from_output()   112    113         # Define or redefine structure information.   114    115         self.populate_objects()   116         self.position_attributes()   117         self.populate_parameters()   118         self.position_parameters()   119         self.populate_tables()   120         self.populate_constants()   121    122     def need_reset(self):   123    124         """   125         Return whether attribute or parameter information has changed, requiring   126         the reset/recompilation of all source files.   127         """   128    129         return self.differing_structures or self.differing_parameters   130    131     def from_output(self):   132    133         "Read input files that influence optimisation."   134    135         # Remove any output for a different program.   136    137         self.check_output()   138    139         # Existing attribute and parameter positioning information. This   140         # influences the positions of attributes and parameters found in the   141         # program.   142    143         locations_filename = self.locations_filename or \   144                              join(self.output, "locations")   145    146         parameter_locations_filename = self.parameter_locations_filename or \   147                                        join(self.output, "parameter_locations")   148    149         self.existing_locations = self.read_data(locations_filename, self._line_to_list, list)   150         self.existing_arg_locations = self.read_data(parameter_locations_filename, self._line_to_list, list)   151    152         # Existing attribute and parameter code information. This is used to   153         # check the compatibility of the output against any assignments   154         # previously made.   155    156         identity = lambda x: x   157         none = lambda x: None   158    159         attrnames_filename = join(self.output, "attrnames")   160         paramnames_filename = join(self.output, "paramnames")   161    162         self.existing_attrnames = self.read_data(attrnames_filename, identity, none)   163         self.existing_paramnames = self.read_data(paramnames_filename, identity, none)   164    165         # Explicitly-specified attribute name and parameter name codes. These   166         # direct assignment of codes in the program.   167    168         self.indicated_attrnames = self.attrnames_filename and \   169                                    self.read_data(self.attrnames_filename, identity, none)   170    171         self.indicated_paramnames = self.paramnames_filename and \   172                                     self.read_data(self.paramnames_filename, identity, none)   173    174         # Existing structure and signature information. This is used to check   175         # the output and detect whether structures or signatures have changed.   176    177         structures_filename = join(self.output, "structures")   178         parameters_filename = join(self.output, "parameters")   179    180         self.existing_structures = dict(self.read_data(structures_filename, self._line_to_structure_pairs, list))   181         self.existing_parameters = dict(self.read_data(parameters_filename, self._line_to_signature_pairs, list))   182    183     def _line_to_list(self, line):   184    185         "Convert comma-separated values in 'line' to a list of values."   186    187         return line.split(", ")   188    189     def _line_to_signature_pairs(self, line):   190    191         "Convert comma-separated values in 'line' to a list of pairs of values."   192    193         l = []   194         objpath, line = line.split(" ", 1)   195         for values in line.split(", "):   196             if values != "-":   197                 name, pos = values.split(":")   198                 l.append((name, int(pos)))   199             else:   200                 l.append(None)   201         return (objpath, l)   202    203     def _line_to_structure_pairs(self, line):   204    205         "Convert comma-separated values in 'line' to a list of pairs of values."   206    207         l = []   208         ref, line = line.split(" ", 1)   209         values = map(lambda x: x != '-' and x or None, line.split(", "))   210         return (decode_reference(ref), values)   211    212     def read_data(self, filename, decode, empty):   213    214         """   215         Read location details from 'filename', using 'decode' to convert each   216         line and 'empty' to produce an empty result where no data is given on a   217         line, returning a collection.   218         """   219    220         collection = []   221    222         if exists(filename):   223             f = open(filename)   224             try:   225                 for line in f.readlines():   226                     line = line.rstrip()   227                     if line:   228                         attrnames = decode(line)   229                     else:   230                         attrnames = empty()   231    232                     collection.append(attrnames)   233             finally:   234                 f.close()   235    236         return collection   237    238     def to_output(self):   239    240         "Write the output files using optimisation information."   241    242         if not exists(self.output):   243             makedirs(self.output)   244    245         self.write_objects()   246    247     def write_objects(self):   248    249         """   250         Write object-related output.   251    252         The locations are a list of positions indicating the attributes residing   253         at each position in the different structures in a program.   254    255         ----   256    257         The parameter locations are a list of positions indicating the parameters   258         residing at each position in the different parameter lists in a program.   259    260         ----   261    262         The structures are presented as a table in the following format:   263    264         qualified name " " attribute names   265    266         The attribute names are separated by ", " characters and indicate the   267         attribute provided at each position in the structure associated with the   268         given type. Where no attribute is provided at a particular location   269         within a structure, "-" is given.   270    271         ----   272    273         The parameters are presented as a table in the following format:   274    275         qualified name " " parameter details   276    277         The parameter details are separated by ", " characters and indicate   278         the parameter name and list position for each parameter described at   279         each location in the parameter table associated with the given   280         function. Where no parameter details are provided at a particular   281         location within a parameter table, "-" is given. The name and list   282         position are separated by a colon (":").   283    284         ----   285    286         The attribute table is presented as a table in the following format:   287    288         qualified name " " attribute identifiers   289    290         Instead of attribute names, identifiers defined according to the order   291         given in the "attrnames" file are employed to denote the attributes   292         featured in each type's structure. Where no attribute is provided at a   293         particular location within a structure, "-" is given.   294    295         ----   296    297         The parameter table is presented as a table in the following format:   298    299         qualified name " " parameter details   300    301         Instead of parameter names, identifiers defined according to the order   302         given in the "paramnames" file are employed to denote the parameters   303         featured in each function's parameter table. Where no parameter is   304         provided at a particular location within a table, "-" is given.   305    306         ----   307    308         The ordered list of attribute names is given in the "attrnames" file.   309    310         ----   311    312         The ordered list of parameter names is given in the "paramnames" file.   313    314         ----   315    316         The ordered list of constant literals is given in the "constants" file.   317         """   318    319         f = open(join(self.output, "locations"), "w")   320         try:   321             for attrnames in self.locations:   322                 print >>f, sorted_output(attrnames)   323    324         finally:   325             f.close()   326    327         f = open(join(self.output, "parameter_locations"), "w")   328         try:   329             for argnames in self.arg_locations:   330                 print >>f, sorted_output(argnames)   331    332         finally:   333             f.close()   334    335         f = open(join(self.output, "structures"), "w")   336         try:   337             structures = self.structures.items()   338             structures.sort()   339    340             for name, attrnames in structures:   341                 print >>f, name, ", ".join([s or "-" for s in attrnames])   342    343         finally:   344             f.close()   345    346         f = open(join(self.output, "parameters"), "w")   347         try:   348             parameters = self.parameters.items()   349             parameters.sort()   350    351             for name, argnames in parameters:   352                 l = []   353                 for s in argnames:   354                     l.append(s and ("%s:%d" % s) or "-")   355                 print >>f, name, ", ".join(l)   356    357         finally:   358             f.close()   359    360         f = open(join(self.output, "attrtable"), "w")   361         try:   362             attr_table = self.attr_table.items()   363             attr_table.sort()   364    365             for name, attrcodes in attr_table:   366                 l = []   367                 for i in attrcodes:   368                     l.append(i is not None and str(i) or "-")   369                 print >>f, name, ", ".join(l)   370    371         finally:   372             f.close()   373    374         f = open(join(self.output, "paramtable"), "w")   375         try:   376             param_table = self.param_table.items()   377             param_table.sort()   378    379             for name, paramcodes in param_table:   380                 l = []   381                 for s in paramcodes:   382                     l.append(s and ("%d:%d" % s) or "-")   383                 print >>f, name, ", ".join(l)   384    385         finally:   386             f.close()   387    388         f = open(join(self.output, "attrnames"), "w")   389         try:   390             for name in self.all_attrnames:   391                 print >>f, name   392    393         finally:   394             f.close()   395    396         f = open(join(self.output, "paramnames"), "w")   397         try:   398             for name in self.all_paramnames:   399                 print >>f, name   400    401         finally:   402             f.close()   403    404         f = open(join(self.output, "constants"), "w")   405         try:   406             constants = []   407             for (value, value_type, encoding), n in self.constants.items():   408                 constants.append((n, value_type, encoding, value))   409             constants.sort()   410             for n, value_type, encoding, value in constants:   411                 print >>f, value_type, encoding or "{}", repr(value)   412    413         finally:   414             f.close()   415    416     def populate_objects(self):   417    418         "Populate objects using attribute and usage information."   419    420         self.all_attrs = {}   421    422         # Partition attributes into separate sections so that class and instance   423         # attributes are treated separately.   424    425         for source, objkind in [   426             (self.importer.all_class_attrs, "<class>"),   427             (self.importer.all_instance_attrs, "<instance>"),   428             (self.importer.all_module_attrs, "<module>")   429             ]:   430    431             for name, attrnames in source.items():   432    433                 # Remove temporary names from structures.   434    435                 attrnames = filter(lambda x: not x.startswith("$t"), attrnames)   436                 self.all_attrs[(objkind, name)] = attrnames   437    438         self.locations = get_allocated_locations(self.all_attrs,   439             get_attributes_and_sizes, self.existing_locations)   440    441     def populate_parameters(self):   442    443         "Populate parameter tables using parameter information."   444    445         # Allocate positions from 1 onwards, ignoring the context argument.   446    447         self.arg_locations = [set()] + get_allocated_locations(   448             self.importer.function_parameters, get_parameters_and_sizes,   449             self.existing_arg_locations[1:])   450    451     def position_attributes(self):   452    453         "Position specific attribute references."   454    455         # Reverse the location mappings, producing a mapping from attribute   456         # names to positions.   457    458         attr_locations = self.attr_locations = {}   459         self._position_attributes(attr_locations, self.locations)   460    461         # Add any previously-known attribute locations. This prevents attributes   462         # from being assigned different identifying codes by preserving obsolete   463         # attribute codes.   464    465         if self.existing_locations:   466             self._position_attributes(attr_locations, self.existing_locations)   467    468         # Record the structures.   469    470         for (objkind, name), attrnames in self.all_attrs.items():   471             key = Reference(objkind, name)   472             l = self.structures[key] = [None] * len(attrnames)   473    474             for attrname in attrnames:   475                 position = attr_locations[attrname]   476                 if position >= len(l):   477                     l.extend([None] * (position - len(l) + 1))   478                 l[position] = attrname   479    480             # Test the structure against any existing attributes.   481    482             if self.existing_structures:   483                 if self.existing_structures.has_key(key) and self.existing_structures[key] != l:   484                     self.differing_structures.append(key)   485    486     def _position_attributes(self, d, l):   487    488         """   489         For each attribute, store a mapping in 'd' to the index in 'l' at which   490         it can be found.   491         """   492    493         for i, attrnames in enumerate(l):   494             for attrname in attrnames:   495                 if not d.has_key(attrname):   496                     d[attrname] = i   497    498     def get_ambiguity_for_attributes(self, attrnames):   499    500         """   501         Return a list of attribute position alternatives corresponding to each   502         of the given 'attrnames'.   503         """   504    505         ambiguity = []   506    507         for attrname in attrnames:   508             position = self.attr_locations[attrname]   509             ambiguity.append(len(self.locations[position]))   510    511         return ambiguity   512    513     def position_parameters(self):   514    515         "Position the parameters for each function's parameter table."   516    517         # Reverse the location mappings, producing a mapping from parameter   518         # names to positions.   519    520         param_locations = self.param_locations = {}   521         self._position_attributes(param_locations, self.arg_locations)   522    523         for name, argnames in self.importer.function_parameters.items():   524    525             # Allocate an extra context parameter in the table.   526    527             l = self.parameters[name] = [None] + [None] * len(argnames)   528    529             # Store an entry for the name along with the name's position in the   530             # parameter list.   531    532             for pos, argname in enumerate(argnames):   533    534                 # Position the argument in the table.   535    536                 position = param_locations[argname]   537                 if position >= len(l):   538                     l.extend([None] * (position - len(l) + 1))   539    540                 # Indicate an argument list position starting from 1 (after the   541                 # initial context argument).   542    543                 l[position] = (argname, pos + 1)   544    545             # Test the structure against any existing parameters.   546    547             if self.existing_parameters:   548                 if self.existing_parameters.has_key(name) and self.existing_parameters[name] != l:   549                     self.differing_parameters.append(name)   550    551     def populate_tables(self):   552    553         """   554         Assign identifiers to attributes and encode structure information using   555         these identifiers.   556         """   557    558         # Initialise the mapping from attribute names to codes.   559    560         l = self.all_attrnames = []; d = {}   561         self._init_name_mapping(l, d, self.existing_attrnames)   562         if self.indicated_attrnames:   563             self._init_name_mapping(l, d, self.indicated_attrnames)   564         self._update_name_mapping(l, d, self.attr_locations)   565    566         # Record the numbers indicating the locations of the names.   567    568         for key, attrnames in self.structures.items():   569             l = self.attr_table[key] = []   570             for attrname in attrnames:   571                 if attrname is None:   572                     l.append(None)   573                 else:   574                     l.append(d[attrname])   575    576         # Initialise the mapping from parameter names to codes.   577    578         l = self.all_paramnames = []; d = {}   579         self._init_name_mapping(l, d, self.existing_paramnames)   580         if self.indicated_paramnames:   581             self._init_name_mapping(l, d, self.indicated_paramnames)   582         self._update_name_mapping(l, d, self.param_locations)   583    584         # Record the numbers indicating the locations of the names.   585    586         for key, values in self.parameters.items():   587             l = self.param_table[key] = []   588             for value in values:   589                 if value is None:   590                     l.append(None)   591                 else:   592                     name, pos = value   593                     l.append((d[name], pos))   594    595     def _init_name_mapping(self, l, d, existing):   596    597         """   598         Initialise the name collection 'l', with mapping 'd', using the   599         'existing' mapping.   600         """   601    602         i = 0   603    604         for name in existing:   605    606             # Test for the name in another position.   607    608             if d.has_key(name):   609                 if d[name] != i:   610                     raise OptimiseError, "Name %s has conflicting codes: %d and %d." % \   611                                          (name, d[name], i)   612             else:   613    614                 # Test for other usage of the position.   615    616                 if i < len(l):   617                     if l[i] != name:   618                         raise OptimiseError, "Position %d has conflicting names: %s and %s." % \   619                                              (i, name, d[name])   620                     l[i] = name   621                 else:   622                     l.append(name)   623    624                 d[name] = i   625    626             i += 1   627    628     def _update_name_mapping(self, l, d, locations):   629    630         """   631         Using any existing identifiers supplied by 'l' and 'd', update the   632         identifiers using a sorted list of names from 'locations'.   633         """   634    635         all_names = list(locations.keys())   636         all_names.sort()   637    638         i = len(l)   639    640         for name in all_names:   641             if not d.has_key(name):   642                 d[name] = i   643                 l.append(name)   644                 i += 1   645    646     def populate_constants(self):   647    648         """   649         Obtain a collection of distinct constant literals, building a mapping   650         from constant references to those in this collection.   651         """   652    653         # Obtain mappings from constant values to identifiers.   654    655         self.constants = {}   656    657         # Establish a mapping from local constant identifiers to consolidated   658         # constant identifiers.   659    660         self.constant_numbers = {}   661    662         for name, constant in self.importer.all_constant_values.items():   663    664             # Each constant is actually (value, value_type, encoding).   665    666             d = digest(constant)   667             self.constants[constant] = d   668    669             # Make sure the digests are really distinct for different   670             # constants.   671    672             if self.digests.has_key(d):   673                 if self.digests[d] != constant:   674                     raise OptimiseError, "Digest %s used for distinct constants %r and %r." % (   675                                          d, self.digests[d], constant)   676             else:   677                 self.digests[d] = constant   678    679             self.constant_numbers[name] = self.constants[constant]   680    681 def combine_rows(a, b):   682     c = []   683     for i, j in zip(a, b):   684         if i is None or j is None:   685             c.append(i or j)   686         else:   687             return None   688     return c   689    690 def get_attributes_and_sizes(d):   691    692     """   693     Get attribute and size information for the object attributes defined by 'd'   694     providing a mapping from (object kind, type name) to attribute names.   695    696     Return a matrix of attributes (each row entry consisting of column values   697     providing attribute names, with value positions corresponding to types   698     providing such attributes), a list of the type names corresponding to the   699     columns in the matrix, and a list of ranked sizes each indicating...   700    701      * a weighted size depending on the kind of object   702      * the minimum size of an object employing an attribute   703      * the number of free columns in the matrix for the attribute   704      * the attribute name itself   705     """   706    707     attrs = {}   708     sizes = {}   709     objkinds = {}   710    711     for objtype, attrnames in d.items():   712         objkind, _name = objtype   713    714         for attrname in attrnames:   715    716             # Record each type supporting the attribute.   717    718             init_item(attrs, attrname, set)   719             attrs[attrname].add(objtype)   720    721             # Maintain a record of the smallest object size supporting the given   722             # attribute.   723    724             if not sizes.has_key(attrname):   725                 sizes[attrname] = len(attrnames)   726             else:   727                 sizes[attrname] = min(sizes[attrname], len(attrnames))   728    729             # Record the object types/kinds supporting the attribute.   730    731             init_item(objkinds, attrname, set)   732             objkinds[attrname].add(objkind)   733    734     # Obtain attribute details in order of size and occupancy.   735    736     all_objtypes = d.keys()   737    738     rsizes = []   739     for attrname, size in sizes.items():   740         priority = "<instance>" in objkinds[attrname] and 0.5 or 1   741         occupied = len(attrs[attrname])   742         key = (priority * size, size, len(all_objtypes) - occupied, attrname)   743         rsizes.append(key)   744    745     rsizes.sort()   746    747     # Make a matrix of attributes.   748    749     matrix = {}   750     for attrname, objtypes in attrs.items():   751    752         # Traverse the object types, adding the attribute name if the object   753         # type supports the attribute, adding None otherwise.   754    755         row = []   756         for objtype in all_objtypes:   757             if objtype in objtypes:   758                 row.append(attrname)   759             else:   760                 row.append(None)   761         matrix[attrname] = row   762    763     return matrix, all_objtypes, rsizes   764    765 def get_parameters_and_sizes(d):   766    767     """   768     Return a matrix of parameters, a list of functions corresponding to columns   769     in the matrix, and a list of ranked sizes each indicating...   770    771      * a weighted size depending on the kind of object   772      * the minimum size of a parameter list employing a parameter   773      * the number of free columns in the matrix for the parameter   774      * the parameter name itself   775    776     This is a slightly simpler version of the above 'get_attributes_and_sizes'   777     function.   778     """   779    780     params = {}   781     sizes = {}   782    783     for name, argnames in d.items():   784         for argname in argnames:   785    786             # Record each function supporting the parameter.   787    788             init_item(params, argname, set)   789             params[argname].add(name)   790    791             # Maintain a record of the smallest parameter list supporting the   792             # given parameter.   793    794             if not sizes.has_key(argname):   795                 sizes[argname] = len(argnames)   796             else:   797                 sizes[argname] = min(sizes[argname], len(argnames))   798    799     # Obtain attribute details in order of size and occupancy.   800    801     names = d.keys()   802    803     rsizes = []   804     for argname, size in sizes.items():   805         occupied = len(params[argname])   806         key = (size, size, len(names) - occupied, argname)   807         rsizes.append(key)   808    809     rsizes.sort()   810    811     # Make a matrix of parameters.   812    813     matrix = {}   814     for argname, types in params.items():   815         row = []   816         for name in names:   817             if name in types:   818                 row.append(argname)   819             else:   820                 row.append(None)   821         matrix[argname] = row   822    823     return matrix, names, rsizes   824    825 def get_allocated_locations(d, fn, existing=None):   826    827     """   828     Return a list where each element corresponds to a structure location and   829     contains a set of attribute names that may be stored at that location, given   830     a mapping 'd' whose keys are (object kind, object name) tuples and whose   831     values are collections of attributes.   832     """   833    834     matrix, names, rsizes = fn(d)   835     allocated = []   836    837     # Verify any existing allocation.   838    839     allocated_attrnames = set()   840    841     if existing:   842         for attrnames in existing:   843    844             # Handle empty positions.   845    846             if not attrnames:   847                 allocated.append([None] * len(names))   848                 continue   849    850             base = None   851    852             for attrname in attrnames:   853    854                 # Skip presumably-removed attribute names.   855    856                 if not matrix.has_key(attrname):   857                     continue   858    859                 # Handle the first attribute name.   860    861                 if base is None:   862                     base = matrix[attrname]   863                     allocated_attrnames.add(attrname)   864                     continue   865    866                 # Combine existing and new attribute positioning.   867    868                 new = combine_rows(base, matrix[attrname])   869    870                 if new:   871                     base = new   872                     allocated_attrnames.add(attrname)   873                 else:   874                     raise OptimiseError, "Attribute %s cannot be explicitly positioned at %d." % \   875                                          (attrname, len(allocated))   876    877             # Handle empty positions.   878    879             if base:   880                 allocated.append(base)   881             else:   882                 allocated.append([None] * len(names))   883    884     # Try to allocate each attribute name in turn.   885    886     x = 0   887     pos = 0   888    889     while x < len(rsizes):   890    891         # Obtain any previous allocation at the current position. Start at the   892         # current attribute looking for allocations to combine.   893    894         if pos < len(allocated):   895             base = allocated[pos]   896             free = base.count(None)   897             y = x   898    899         # Obtain the object information for the attribute name.   900    901         else:   902             weight, size, free, attrname = rsizes[x]   903    904             # Ignore allocated attribute names.   905    906             if attrname in allocated_attrnames:   907                 x += 1   908                 continue   909    910             # Start at the next attribute looking for allocations to combine.   911    912             base = matrix[attrname]   913             y = x + 1   914    915         # Examine attribute names that follow in the ranking, attempting to   916         # accumulate compatible attributes that can co-exist in the same   917         # position within structures.   918    919         while y < len(rsizes):   920             _weight, _size, _free, _attrname = rsizes[y]   921    922             # Ignore allocated attribute names.   923    924             if _attrname in allocated_attrnames:   925                 y += 1   926                 continue   927    928             # Determine whether this attribute is supported by too many types   929             # to co-exist.   930    931             occupied = len(names) - _free   932             if occupied > free:   933                 break   934    935             # Merge the attribute support for both this and the currently   936             # considered attribute, testing for conflicts. Adopt the merged row   937             # details if they do not conflict.   938    939             new = combine_rows(base, matrix[_attrname])   940             if new:   941                 del matrix[_attrname]   942                 del rsizes[y]   943                 base = new   944                 free -= occupied   945    946             # Otherwise, look for other compatible attributes.   947    948             else:   949                 y += 1   950    951         # Allocate the merged details at the current position.   952    953         if pos < len(allocated):   954             allocated[pos] = base   955             pos += 1   956         else:   957             x += 1   958             allocated.append(base)   959    960     return allocations_to_sets(allocated)   961    962 def allocations_to_sets(allocated):   963    964     """   965     Return the list of attribute names from each row of the 'allocated'   966     attributes table.   967     """   968    969     locations = []   970    971     for attrnames in allocated:   972         l = set()   973    974         # Convert populated allocations.   975    976         if attrnames:   977             for attrname in attrnames:   978                 if attrname:   979                     l.add(attrname)   980    981         locations.append(l)   982    983     return locations   984    985 # vim: tabstop=4 expandtab shiftwidth=4