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