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