Lichen

Annotated optimiser.py

229:f3ea117e51db
2016-11-24 Paul Boddie Make sure that variable references are assigned to names appropriately.
paul@92 1
#!/usr/bin/env python
paul@92 2
paul@92 3
"""
paul@95 4
Optimise object layouts and generate access instruction plans.
paul@92 5
paul@92 6
Copyright (C) 2014, 2015, 2016 Paul Boddie <paul@boddie.org.uk>
paul@92 7
paul@92 8
This program is free software; you can redistribute it and/or modify it under
paul@92 9
the terms of the GNU General Public License as published by the Free Software
paul@92 10
Foundation; either version 3 of the License, or (at your option) any later
paul@92 11
version.
paul@92 12
paul@92 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@92 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@92 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@92 16
details.
paul@92 17
paul@92 18
You should have received a copy of the GNU General Public License along with
paul@92 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@92 20
"""
paul@92 21
paul@92 22
from common import add_counter_item, get_attrname_from_location, init_item, \
paul@92 23
                   sorted_output
paul@94 24
from encoders import encode_access_location, encode_instruction, get_kinds
paul@92 25
from os.path import exists, join
paul@92 26
from os import makedirs
paul@92 27
from referencing import Reference
paul@92 28
paul@92 29
class Optimiser:
paul@92 30
paul@92 31
    "Optimise objects in a program."
paul@92 32
paul@92 33
    def __init__(self, importer, deducer, output):
paul@92 34
paul@92 35
        """
paul@92 36
        Initialise an instance using the given 'importer' and 'deducer' that
paul@92 37
        will perform the arrangement of attributes for program objects, writing
paul@92 38
        the results to the given 'output' directory.
paul@92 39
        """
paul@92 40
paul@92 41
        self.importer = importer
paul@92 42
        self.deducer = deducer
paul@92 43
        self.output = output
paul@92 44
paul@92 45
        # Locations/offsets of attributes in objects.
paul@92 46
paul@92 47
        self.locations = None
paul@92 48
        self.attr_locations = None
paul@92 49
        self.all_attrnames = None
paul@92 50
paul@92 51
        # Locations of parameters in parameter tables.
paul@92 52
paul@92 53
        self.arg_locations = None
paul@92 54
        self.param_locations = None
paul@92 55
        self.all_paramnames = None
paul@92 56
paul@92 57
        # Specific attribute access information.
paul@92 58
paul@94 59
        self.access_instructions = {}
paul@92 60
paul@92 61
        # Object structure information.
paul@92 62
paul@92 63
        self.structures = {}
paul@92 64
        self.attr_table = {}
paul@92 65
paul@92 66
        # Parameter list information.
paul@92 67
paul@92 68
        self.parameters = {}
paul@92 69
        self.param_table = {}
paul@92 70
paul@92 71
        # Constant literal information.
paul@92 72
paul@92 73
        self.constants = []
paul@92 74
        self.constant_numbers = {}
paul@92 75
paul@92 76
        # Optimiser activities.
paul@92 77
paul@92 78
        self.populate_objects()
paul@92 79
        self.position_attributes()
paul@92 80
        self.populate_parameters()
paul@92 81
        self.position_parameters()
paul@92 82
        self.populate_tables()
paul@92 83
        self.populate_constants()
paul@94 84
        self.initialise_access_instructions()
paul@92 85
paul@92 86
    def to_output(self):
paul@92 87
paul@92 88
        "Write the output files using optimisation information."
paul@92 89
paul@92 90
        if not exists(self.output):
paul@92 91
            makedirs(self.output)
paul@92 92
paul@92 93
        self.write_objects()
paul@92 94
paul@92 95
    def write_objects(self):
paul@92 96
paul@92 97
        """
paul@92 98
        Write object-related output.
paul@92 99
paul@92 100
        The locations are a list of positions indicating the attributes residing
paul@92 101
        at each position in the different structures in a program.
paul@92 102
paul@92 103
        ----
paul@92 104
paul@92 105
        The parameter locations are a list of positions indicating the parameters
paul@92 106
        residing at each position in the different parameter lists in a program.
paul@92 107
paul@92 108
        ----
paul@92 109
paul@92 110
        Each attribute plan provides attribute details in the following format:
paul@92 111
paul@92 112
        location " " name " " test " " test type " " base
paul@92 113
                 " " traversed attributes " " traversed attribute ambiguity
paul@96 114
                 " " traversal access modes
paul@92 115
                 " " attributes to traverse " " attribute ambiguity
paul@92 116
                 " " context " " access method " " static attribute
paul@92 117
paul@92 118
        Locations have the following format:
paul@92 119
paul@92 120
        qualified name of scope "." local name ":" name version
paul@92 121
paul@96 122
        Traversal access modes are either "class" (obtain accessor class to
paul@96 123
        access attribute) or "object" (obtain attribute directly from accessor).
paul@96 124
paul@92 125
        ----
paul@92 126
paul@92 127
        The structures are presented as a table in the following format:
paul@92 128
paul@92 129
        qualified name " " attribute names
paul@92 130
paul@92 131
        The attribute names are separated by ", " characters and indicate the
paul@92 132
        attribute provided at each position in the structure associated with the
paul@92 133
        given type. Where no attribute is provided at a particular location
paul@92 134
        within a structure, "-" is given.
paul@92 135
paul@92 136
        ----
paul@92 137
paul@92 138
        The parameters are presented as a table in the following format:
paul@92 139
paul@92 140
        qualified name " " parameter details
paul@92 141
paul@92 142
        The parameter details are separated by ", " characters and indicate
paul@92 143
        the parameter name and list position for each parameter described at
paul@92 144
        each location in the parameter table associated with the given
paul@92 145
        function. Where no parameter details are provided at a particular
paul@92 146
        location within a parameter table, "-" is given. The name and list
paul@92 147
        position are separated by a colon (":").
paul@92 148
paul@92 149
        ----
paul@92 150
paul@92 151
        The attribute table is presented as a table in the following format:
paul@92 152
paul@92 153
        qualified name " " attribute identifiers
paul@92 154
paul@92 155
        Instead of attribute names, identifiers defined according to the order
paul@92 156
        given in the "attrnames" file are employed to denote the attributes
paul@92 157
        featured in each type's structure. Where no attribute is provided at a
paul@92 158
        particular location within a structure, "-" is given.
paul@92 159
paul@92 160
        ----
paul@92 161
paul@92 162
        The parameter table is presented as a table in the following format:
paul@92 163
paul@92 164
        qualified name " " parameter details
paul@92 165
paul@92 166
        Instead of parameter names, identifiers defined according to the order
paul@92 167
        given in the "paramnames" file are employed to denote the parameters
paul@92 168
        featured in each function's parameter table. Where no parameter is
paul@92 169
        provided at a particular location within a table, "-" is given.
paul@92 170
paul@92 171
        ----
paul@92 172
paul@92 173
        The ordered list of attribute names is given in the "attrnames" file.
paul@92 174
paul@92 175
        ----
paul@92 176
paul@92 177
        The ordered list of parameter names is given in the "paramnames" file.
paul@92 178
paul@92 179
        ----
paul@92 180
paul@92 181
        The ordered list of constant literals is given in the "constants" file.
paul@92 182
        """
paul@92 183
paul@92 184
        f = open(join(self.output, "locations"), "w")
paul@92 185
        try:
paul@92 186
            for attrnames in self.locations:
paul@92 187
                print >>f, sorted_output(attrnames)
paul@92 188
paul@92 189
        finally:
paul@92 190
            f.close()
paul@92 191
paul@92 192
        f = open(join(self.output, "parameter_locations"), "w")
paul@92 193
        try:
paul@92 194
            for argnames in self.arg_locations:
paul@92 195
                print >>f, sorted_output(argnames)
paul@92 196
paul@92 197
        finally:
paul@92 198
            f.close()
paul@92 199
paul@94 200
        f = open(join(self.output, "instruction_plans"), "w")
paul@94 201
        try:
paul@94 202
            access_instructions = self.access_instructions.items()
paul@94 203
            access_instructions.sort()
paul@94 204
paul@94 205
            for location, instructions in access_instructions:
paul@94 206
                print >>f, encode_access_location(location), "..."
paul@94 207
                for instruction in instructions:
paul@94 208
                    print >>f, encode_instruction(instruction)
paul@94 209
                print >>f
paul@92 210
paul@92 211
        finally:
paul@92 212
            f.close()
paul@92 213
paul@92 214
        f = open(join(self.output, "structures"), "w")
paul@92 215
        try:
paul@92 216
            structures = self.structures.items()
paul@92 217
            structures.sort()
paul@92 218
paul@92 219
            for name, attrnames in structures:
paul@92 220
                print >>f, name, ", ".join([s or "-" for s in attrnames])
paul@92 221
paul@92 222
        finally:
paul@92 223
            f.close()
paul@92 224
paul@92 225
        f = open(join(self.output, "parameters"), "w")
paul@92 226
        try:
paul@92 227
            parameters = self.parameters.items()
paul@92 228
            parameters.sort()
paul@92 229
paul@92 230
            for name, argnames in parameters:
paul@92 231
                print >>f, name, ", ".join([s and ("%s:%d" % s) or "-" for s in argnames])
paul@92 232
paul@92 233
        finally:
paul@92 234
            f.close()
paul@92 235
paul@92 236
        f = open(join(self.output, "attrtable"), "w")
paul@92 237
        try:
paul@92 238
            attr_table = self.attr_table.items()
paul@92 239
            attr_table.sort()
paul@92 240
paul@92 241
            for name, attrcodes in attr_table:
paul@92 242
                print >>f, name, ", ".join([i is not None and str(i) or "-" for i in attrcodes])
paul@92 243
paul@92 244
        finally:
paul@92 245
            f.close()
paul@92 246
paul@92 247
        f = open(join(self.output, "paramtable"), "w")
paul@92 248
        try:
paul@92 249
            param_table = self.param_table.items()
paul@92 250
            param_table.sort()
paul@92 251
paul@92 252
            for name, paramcodes in param_table:
paul@92 253
                print >>f, name, ", ".join([s and ("%d:%d" % s) or "-" for s in paramcodes])
paul@92 254
paul@92 255
        finally:
paul@92 256
            f.close()
paul@92 257
paul@92 258
        f = open(join(self.output, "attrnames"), "w")
paul@92 259
        try:
paul@92 260
            for name in self.all_attrnames:
paul@92 261
                print >>f, name
paul@92 262
paul@92 263
        finally:
paul@92 264
            f.close()
paul@92 265
paul@92 266
        f = open(join(self.output, "paramnames"), "w")
paul@92 267
        try:
paul@92 268
            for name in self.all_paramnames:
paul@92 269
                print >>f, name
paul@92 270
paul@92 271
        finally:
paul@92 272
            f.close()
paul@92 273
paul@92 274
        f = open(join(self.output, "constants"), "w")
paul@92 275
        try:
paul@92 276
            constants = [(n, value) for (value, n) in self.constants.items()]
paul@92 277
            constants.sort()
paul@92 278
            for n, value in constants:
paul@92 279
                print >>f, repr(value)
paul@92 280
paul@92 281
        finally:
paul@92 282
            f.close()
paul@92 283
paul@92 284
    def populate_objects(self):
paul@92 285
paul@92 286
        "Populate objects using attribute and usage information."
paul@92 287
paul@92 288
        all_attrs = {}
paul@92 289
paul@92 290
        # Partition attributes into separate sections so that class and instance
paul@92 291
        # attributes are treated separately.
paul@92 292
paul@92 293
        for source, objtype in [
paul@92 294
            (self.importer.all_class_attrs, "<class>"),
paul@92 295
            (self.importer.all_instance_attrs, "<instance>"),
paul@92 296
            (self.importer.all_module_attrs, "<module>")
paul@92 297
            ]:
paul@92 298
            for name, attrs in source.items():
paul@92 299
                all_attrs[(objtype, name)] = attrs
paul@92 300
paul@92 301
        self.locations = get_allocated_locations(all_attrs, get_attributes_and_sizes)
paul@92 302
paul@92 303
    def populate_parameters(self):
paul@92 304
paul@92 305
        "Populate parameter tables using parameter information."
paul@92 306
paul@130 307
        self.arg_locations = [set()] + get_allocated_locations(self.importer.function_parameters, get_parameters_and_sizes)
paul@92 308
paul@92 309
    def position_attributes(self):
paul@92 310
paul@92 311
        "Position specific attribute references."
paul@92 312
paul@92 313
        # Reverse the location mappings.
paul@92 314
paul@92 315
        attr_locations = self.attr_locations = {}
paul@92 316
paul@92 317
        for i, attrnames in enumerate(self.locations):
paul@92 318
            for attrname in attrnames:
paul@92 319
                attr_locations[attrname] = i
paul@92 320
paul@92 321
        # Record the structures.
paul@92 322
paul@92 323
        for source, objtype in [
paul@92 324
            (self.importer.all_class_attrs, "<class>"),
paul@92 325
            (self.importer.all_instance_attrs, "<instance>"),
paul@92 326
            (self.importer.all_module_attrs, "<module>")
paul@92 327
            ]:
paul@92 328
paul@92 329
            for name, attrnames in source.items():
paul@92 330
                key = Reference(objtype, name)
paul@92 331
                l = self.structures[key] = [None] * len(attrnames)
paul@92 332
                for attrname in attrnames:
paul@92 333
                    position = attr_locations[attrname]
paul@92 334
                    if position >= len(l):
paul@92 335
                        l.extend([None] * (position - len(l) + 1))
paul@92 336
                    l[position] = attrname
paul@92 337
paul@94 338
    def initialise_access_instructions(self):
paul@94 339
paul@94 340
        "Expand access plans into instruction sequences."
paul@94 341
paul@97 342
        for access_location, access_plan in self.deducer.access_plans.items():
paul@94 343
paul@94 344
            # Obtain the access details.
paul@94 345
paul@96 346
            name, test, test_type, base, traversed, traversal_modes, \
paul@102 347
                attrnames, context, context_test, first_method, final_method, origin = access_plan
paul@94 348
paul@94 349
            instructions = []
paul@94 350
            emit = instructions.append
paul@94 351
paul@94 352
            if base:
paul@94 353
                original_accessor = base
paul@94 354
            else:
paul@95 355
                original_accessor = "<expr>" # use a generic placeholder
paul@94 356
paul@94 357
            # Prepare for any first attribute access.
paul@94 358
paul@94 359
            if traversed:
paul@94 360
                attrname = traversed[0]
paul@94 361
                del traversed[0]
paul@94 362
            elif attrnames:
paul@94 363
                attrname = attrnames[0]
paul@94 364
                del attrnames[0]
paul@94 365
paul@98 366
            # Perform the first access explicitly if at least one operation
paul@98 367
            # requires it.
paul@98 368
paul@98 369
            access_first_attribute = final_method in ("access", "assign") or traversed or attrnames
paul@98 370
paul@98 371
            # Determine whether the first access involves assignment.
paul@98 372
paul@98 373
            assigning = not traversed and not attrnames and final_method == "assign"
paul@94 374
paul@94 375
            # Set the context if already available.
paul@100 376
paul@103 377
            if context == "base":
paul@103 378
                accessor = context_var = (base,)
paul@103 379
            elif context == "original-accessor":
paul@104 380
paul@104 381
                # Prevent re-evaluation of any dynamic expression by storing it.
paul@104 382
paul@103 383
                if original_accessor == "<expr>":
paul@113 384
                    emit(("__set_accessor", original_accessor))
paul@113 385
                    accessor = context_var = ("<accessor>",)
paul@103 386
                else:
paul@104 387
                    accessor = context_var = (original_accessor,)
paul@100 388
paul@98 389
            # Assigning does not set the context.
paul@94 390
paul@102 391
            elif context in ("final-accessor", "unset") and access_first_attribute:
paul@104 392
paul@104 393
                # Prevent re-evaluation of any dynamic expression by storing it.
paul@104 394
paul@103 395
                if original_accessor == "<expr>":
paul@113 396
                    emit(("__set_accessor", original_accessor))
paul@113 397
                    accessor = ("<accessor>",)
paul@103 398
                else:
paul@104 399
                    accessor = (original_accessor,)
paul@94 400
paul@94 401
            # Apply any test.
paul@94 402
paul@95 403
            if test == "specific-type":
paul@113 404
                accessor = ("__test_specific_type", accessor, test_type)
paul@95 405
            elif test == "specific-instance":
paul@113 406
                accessor = ("__test_specific_instance", accessor, test_type)
paul@95 407
            elif test == "specific-object":
paul@113 408
                accessor = ("__test_specific_object", accessor, test_type)
paul@95 409
            elif test == "common-type":
paul@113 410
                accessor = ("__test_common_type", accessor, test_type)
paul@95 411
            elif test == "common-instance":
paul@113 412
                accessor = ("__test_common_instance", accessor, test_type)
paul@95 413
            elif test == "common-object":
paul@113 414
                accessor = ("__test_common_object", accessor, test_type)
paul@94 415
paul@94 416
            # Perform the first or final access.
paul@94 417
            # The access only needs performing if the resulting accessor is used.
paul@94 418
paul@102 419
            remaining = len(traversed + attrnames)
paul@102 420
paul@94 421
            if access_first_attribute:
paul@94 422
paul@94 423
                if first_method == "relative-class":
paul@98 424
                    if assigning:
paul@113 425
                        emit(("__store_via_class", accessor, attrname, "<assexpr>"))
paul@98 426
                    else:
paul@113 427
                        accessor = ("__load_via_class", accessor, attrname)
paul@98 428
paul@94 429
                elif first_method == "relative-object":
paul@98 430
                    if assigning:
paul@113 431
                        emit(("__store_via_object", accessor, attrname, "<assexpr>"))
paul@98 432
                    else:
paul@113 433
                        accessor = ("__load_via_object", accessor, attrname)
paul@98 434
paul@94 435
                elif first_method == "relative-object-class":
paul@98 436
                    if assigning:
paul@113 437
                        emit(("__get_class_and_store", accessor, attrname, "<assexpr>"))
paul@98 438
                    else:
paul@113 439
                        accessor = ("__get_class_and_load", accessor, attrname)
paul@98 440
paul@94 441
                elif first_method == "check-class":
paul@98 442
                    if assigning:
paul@113 443
                        emit(("__check_and_store_via_class", accessor, attrname, "<assexpr>"))
paul@98 444
                    else:
paul@113 445
                        accessor = ("__check_and_load_via_class", accessor, attrname)
paul@98 446
paul@94 447
                elif first_method == "check-object":
paul@98 448
                    if assigning:
paul@113 449
                        emit(("__check_and_store_via_object", accessor, attrname, "<assexpr>"))
paul@98 450
                    else:
paul@113 451
                        accessor = ("__check_and_load_via_object", accessor, attrname)
paul@98 452
paul@94 453
                elif first_method == "check-object-class":
paul@98 454
                    if assigning:
paul@113 455
                        emit(("__check_and_store_via_any", accessor, attrname, "<assexpr>"))
paul@98 456
                    else:
paul@113 457
                        accessor = ("__check_and_load_via_any", accessor, attrname)
paul@94 458
paul@102 459
            # Traverse attributes using the accessor.
paul@94 460
paul@94 461
            if traversed:
paul@96 462
                for attrname, traversal_mode in zip(traversed, traversal_modes):
paul@98 463
                    assigning = remaining == 1 and final_method == "assign"
paul@94 464
paul@94 465
                    # Set the context, if appropriate.
paul@94 466
paul@98 467
                    if remaining == 1 and final_method != "assign" and context == "final-accessor":
paul@113 468
                        emit(("__set_context", accessor))
paul@113 469
                        accessor = context_var = "<context>"
paul@94 470
paul@94 471
                    # Perform the access only if not achieved directly.
paul@94 472
paul@98 473
                    if remaining > 1 or final_method in ("access", "assign"):
paul@98 474
paul@96 475
                        if traversal_mode == "class":
paul@98 476
                            if assigning:
paul@113 477
                                emit(("__store_via_class", accessor, attrname, "<assexpr>"))
paul@98 478
                            else:
paul@113 479
                                accessor = ("__load_via_class", accessor, attrname)
paul@96 480
                        else:
paul@98 481
                            if assigning:
paul@113 482
                                emit(("__store_via_object", accessor, attrname, "<assexpr>"))
paul@98 483
                            else:
paul@113 484
                                accessor = ("__load_via_object", accessor, attrname)
paul@94 485
paul@94 486
                    remaining -= 1
paul@94 487
paul@94 488
            if attrnames:
paul@96 489
                for attrname in attrnames:
paul@98 490
                    assigning = remaining == 1 and final_method == "assign"
paul@94 491
paul@94 492
                    # Set the context, if appropriate.
paul@94 493
paul@98 494
                    if remaining == 1 and final_method != "assign" and context == "final-accessor":
paul@113 495
                        emit(("__set_context", accessor))
paul@113 496
                        accessor = context_var = "<context>"
paul@94 497
paul@94 498
                    # Perform the access only if not achieved directly.
paul@94 499
paul@98 500
                    if remaining > 1 or final_method in ("access", "assign"):
paul@98 501
paul@98 502
                        if assigning:
paul@113 503
                            emit(("__check_and_store_via_any", accessor, attrname, "<assexpr>"))
paul@98 504
                        else:
paul@113 505
                            accessor = ("__check_and_load_via_any", accessor, attrname)
paul@94 506
paul@94 507
                    remaining -= 1
paul@94 508
paul@118 509
            # Define or emit the means of accessing the actual target.
paul@118 510
paul@98 511
            if final_method == "static-assign":
paul@118 512
                parent, attrname = origin.rsplit(".", 1)
paul@118 513
                emit(("__store_via_object", parent, attrname, "<assexpr>"))
paul@118 514
paul@200 515
            elif final_method in ("static", "static-invoke"):
paul@200 516
                parent, attrname = origin.rsplit(".", 1)
paul@200 517
                accessor = ("__load_static", parent, origin)
paul@118 518
paul@118 519
            # Wrap accesses in context operations.
paul@118 520
paul@102 521
            if context_test == "test":
paul@113 522
                emit(("__test_context", context_var, accessor))
paul@118 523
paul@102 524
            elif context_test == "replace":
paul@118 525
paul@118 526
                # Static invocation targets have a context added but no other
paul@118 527
                # transformation performed.
paul@118 528
paul@118 529
                if final_method == "static-invoke":
paul@118 530
                    emit(("__update_context", context_var, accessor))
paul@118 531
paul@118 532
                # Other invocation targets gain a context and have the bound
paul@118 533
                # version of the callable activated.
paul@118 534
paul@118 535
                else:
paul@118 536
                    emit(("__replace_context", context_var, accessor))
paul@118 537
paul@103 538
            elif final_method not in ("assign", "static-assign"):
paul@103 539
                emit(accessor)
paul@94 540
paul@94 541
            self.access_instructions[access_location] = instructions
paul@92 542
paul@92 543
    def get_ambiguity_for_attributes(self, attrnames):
paul@92 544
paul@92 545
        """
paul@92 546
        Return a list of attribute position alternatives corresponding to each
paul@92 547
        of the given 'attrnames'.
paul@92 548
        """
paul@92 549
paul@92 550
        ambiguity = []
paul@92 551
paul@92 552
        for attrname in attrnames:
paul@92 553
            position = self.attr_locations[attrname]
paul@92 554
            ambiguity.append(len(self.locations[position]))
paul@92 555
paul@92 556
        return ambiguity
paul@92 557
paul@92 558
    def position_parameters(self):
paul@92 559
paul@92 560
        "Position the parameters for each function's parameter table."
paul@92 561
paul@92 562
        # Reverse the location mappings.
paul@92 563
paul@92 564
        param_locations = self.param_locations = {}
paul@92 565
paul@92 566
        for i, argnames in enumerate(self.arg_locations):
paul@125 567
paul@130 568
            # Position the arguments.
paul@125 569
paul@92 570
            for argname in argnames:
paul@130 571
                param_locations[argname] = i
paul@92 572
paul@92 573
        for name, argnames in self.importer.function_parameters.items():
paul@125 574
paul@125 575
            # Allocate an extra context parameter in the table.
paul@125 576
paul@133 577
            l = self.parameters[name] = [None] + [None] * len(argnames)
paul@92 578
paul@92 579
            # Store an entry for the name along with the name's position in the
paul@92 580
            # parameter list.
paul@92 581
paul@92 582
            for pos, argname in enumerate(argnames):
paul@125 583
paul@125 584
                # Position the argument in the table.
paul@125 585
paul@92 586
                position = param_locations[argname]
paul@92 587
                if position >= len(l):
paul@92 588
                    l.extend([None] * (position - len(l) + 1))
paul@125 589
paul@125 590
                # Indicate an argument list position starting from 1 (after the
paul@125 591
                # initial context argument).
paul@125 592
paul@133 593
                l[position] = (argname, pos + 1)
paul@92 594
paul@92 595
    def populate_tables(self):
paul@92 596
paul@92 597
        """
paul@92 598
        Assign identifiers to attributes and encode structure information using
paul@92 599
        these identifiers.
paul@92 600
        """
paul@92 601
paul@92 602
        self.all_attrnames, d = self._get_name_mapping(self.attr_locations)
paul@92 603
paul@92 604
        # Record the numbers indicating the locations of the names.
paul@92 605
paul@92 606
        for key, attrnames in self.structures.items():
paul@92 607
            l = self.attr_table[key] = []
paul@92 608
            for attrname in attrnames:
paul@92 609
                if attrname is None:
paul@92 610
                    l.append(None)
paul@92 611
                else:
paul@92 612
                    l.append(d[attrname])
paul@92 613
paul@92 614
        self.all_paramnames, d = self._get_name_mapping(self.param_locations)
paul@92 615
paul@92 616
        # Record the numbers indicating the locations of the names.
paul@92 617
paul@92 618
        for key, values in self.parameters.items():
paul@92 619
            l = self.param_table[key] = []
paul@92 620
            for value in values:
paul@92 621
                if value is None:
paul@92 622
                    l.append(None)
paul@92 623
                else:
paul@92 624
                    name, pos = value
paul@92 625
                    l.append((d[name], pos))
paul@92 626
paul@92 627
    def _get_name_mapping(self, locations):
paul@92 628
paul@92 629
        """
paul@92 630
        Get a sorted list of names from 'locations', then map them to
paul@92 631
        identifying numbers. Return the list and the mapping.
paul@92 632
        """
paul@92 633
paul@92 634
        all_names = locations.keys()
paul@92 635
        all_names.sort()
paul@92 636
        return all_names, dict([(name, i) for i, name in enumerate(all_names)])
paul@92 637
paul@92 638
    def populate_constants(self):
paul@92 639
paul@92 640
        """
paul@92 641
        Obtain a collection of distinct constant literals, building a mapping
paul@92 642
        from constant references to those in this collection.
paul@92 643
        """
paul@92 644
paul@92 645
        # Obtain mappings from constant values to identifiers.
paul@92 646
paul@92 647
        self.constants = {}
paul@92 648
paul@92 649
        for path, constants in self.importer.all_constants.items():
paul@92 650
            for constant, n in constants.items():
paul@92 651
paul@92 652
                # Record constants and obtain a number for them.
paul@92 653
paul@92 654
                add_counter_item(self.constants, constant)
paul@92 655
paul@92 656
        self.constant_numbers = {}
paul@92 657
paul@92 658
        for name, (value, value_type) in self.importer.all_constant_values.items():
paul@92 659
            self.constant_numbers[name] = self.constants[value]
paul@92 660
paul@92 661
def combine_rows(a, b):
paul@92 662
    c = []
paul@92 663
    for i, j in zip(a, b):
paul@92 664
        if i is None or j is None:
paul@92 665
            c.append(i or j)
paul@92 666
        else:
paul@92 667
            return None
paul@92 668
    return c
paul@92 669
paul@92 670
def get_attributes_and_sizes(d):
paul@92 671
paul@92 672
    """
paul@92 673
    Return a matrix of attributes, a list of type names corresponding to columns
paul@92 674
    in the matrix, and a list of ranked sizes each indicating...
paul@92 675
paul@92 676
     * a weighted size depending on the kind of object
paul@92 677
     * the minimum size of an object employing an attribute
paul@92 678
     * the number of free columns in the matrix for the attribute
paul@92 679
     * the attribute name itself
paul@92 680
    """
paul@92 681
paul@92 682
    attrs = {}
paul@92 683
    sizes = {}
paul@92 684
    objtypes = {}
paul@92 685
paul@92 686
    for name, attrnames in d.items():
paul@92 687
        objtype, _name = name
paul@92 688
paul@92 689
        for attrname in attrnames:
paul@92 690
paul@92 691
            # Record each type supporting the attribute.
paul@92 692
paul@92 693
            init_item(attrs, attrname, set)
paul@92 694
            attrs[attrname].add(name)
paul@92 695
paul@92 696
            # Maintain a record of the smallest object size supporting the given
paul@92 697
            # attribute.
paul@92 698
paul@92 699
            if not sizes.has_key(attrname):
paul@92 700
                sizes[attrname] = len(attrnames)
paul@92 701
            else:
paul@92 702
                sizes[attrname] = min(sizes[attrname], len(attrnames))
paul@92 703
paul@92 704
            # Record the object types/kinds supporting the attribute.
paul@92 705
paul@92 706
            init_item(objtypes, attrname, set)
paul@92 707
            objtypes[attrname].add(objtype)
paul@92 708
paul@92 709
    # Obtain attribute details in order of size and occupancy.
paul@92 710
paul@92 711
    names = d.keys()
paul@92 712
paul@92 713
    rsizes = []
paul@92 714
    for attrname, size in sizes.items():
paul@92 715
        priority = "<instance>" in objtypes[attrname] and 0.5 or 1
paul@92 716
        occupied = len(attrs[attrname])
paul@92 717
        key = (priority * size, size, len(names) - occupied, attrname)
paul@92 718
        rsizes.append(key)
paul@92 719
paul@92 720
    rsizes.sort()
paul@92 721
paul@92 722
    # Make a matrix of attributes.
paul@92 723
paul@92 724
    matrix = {}
paul@92 725
    for attrname, types in attrs.items():
paul@92 726
        row = []
paul@92 727
        for name in names:
paul@92 728
            if name in types:
paul@92 729
                row.append(attrname)
paul@92 730
            else:
paul@92 731
                row.append(None)
paul@92 732
        matrix[attrname] = row
paul@92 733
paul@92 734
    return matrix, names, rsizes
paul@92 735
paul@92 736
def get_parameters_and_sizes(d):
paul@92 737
paul@92 738
    """
paul@92 739
    Return a matrix of parameters, a list of functions corresponding to columns
paul@92 740
    in the matrix, and a list of ranked sizes each indicating...
paul@92 741
paul@92 742
     * a weighted size depending on the kind of object
paul@92 743
     * the minimum size of a parameter list employing a parameter
paul@92 744
     * the number of free columns in the matrix for the parameter
paul@92 745
     * the parameter name itself
paul@92 746
paul@92 747
    This is a slightly simpler version of the above 'get_attributes_and_sizes'
paul@92 748
    function.
paul@92 749
    """
paul@92 750
paul@92 751
    params = {}
paul@92 752
    sizes = {}
paul@92 753
paul@92 754
    for name, argnames in d.items():
paul@92 755
        for argname in argnames:
paul@92 756
paul@92 757
            # Record each function supporting the parameter.
paul@92 758
paul@92 759
            init_item(params, argname, set)
paul@92 760
            params[argname].add(name)
paul@92 761
paul@92 762
            # Maintain a record of the smallest parameter list supporting the
paul@92 763
            # given parameter.
paul@92 764
paul@92 765
            if not sizes.has_key(argname):
paul@92 766
                sizes[argname] = len(argnames)
paul@92 767
            else:
paul@92 768
                sizes[argname] = min(sizes[argname], len(argnames))
paul@92 769
paul@92 770
    # Obtain attribute details in order of size and occupancy.
paul@92 771
paul@92 772
    names = d.keys()
paul@92 773
paul@92 774
    rsizes = []
paul@92 775
    for argname, size in sizes.items():
paul@92 776
        occupied = len(params[argname])
paul@92 777
        key = (size, size, len(names) - occupied, argname)
paul@92 778
        rsizes.append(key)
paul@92 779
paul@92 780
    rsizes.sort()
paul@92 781
paul@92 782
    # Make a matrix of parameters.
paul@92 783
paul@92 784
    matrix = {}
paul@92 785
    for argname, types in params.items():
paul@92 786
        row = []
paul@92 787
        for name in names:
paul@92 788
            if name in types:
paul@92 789
                row.append(argname)
paul@92 790
            else:
paul@92 791
                row.append(None)
paul@92 792
        matrix[argname] = row
paul@92 793
paul@92 794
    return matrix, names, rsizes
paul@92 795
paul@92 796
def get_allocated_locations(d, fn):
paul@92 797
paul@92 798
    """
paul@92 799
    Return a list where each element corresponds to a structure location and
paul@92 800
    contains a set of attribute names that may be stored at that location, given
paul@92 801
    a mapping 'd' whose keys are (object type, object name) tuples and whose
paul@92 802
    values are collections of attributes.
paul@92 803
    """
paul@92 804
paul@92 805
    matrix, names, rsizes = fn(d)
paul@92 806
    allocated = []
paul@92 807
paul@92 808
    x = 0
paul@92 809
    while x < len(rsizes):
paul@92 810
        weight, size, free, attrname = rsizes[x]
paul@92 811
        base = matrix[attrname]
paul@92 812
        y = x + 1
paul@92 813
        while y < len(rsizes):
paul@92 814
            _weight, _size, _free, _attrname = rsizes[y]
paul@92 815
            occupied = len(names) - _free
paul@92 816
            if occupied > free:
paul@92 817
                break
paul@92 818
            new = combine_rows(base, matrix[_attrname])
paul@92 819
            if new:
paul@92 820
                del matrix[_attrname]
paul@92 821
                del rsizes[y]
paul@92 822
                base = new
paul@92 823
                free -= occupied
paul@92 824
            else:
paul@92 825
                y += 1
paul@92 826
        allocated.append(base)
paul@92 827
        x += 1
paul@92 828
paul@92 829
    # Return the list of attribute names from each row of the allocated
paul@92 830
    # attributes table.
paul@92 831
paul@130 832
    locations = []
paul@130 833
    for attrnames in allocated:
paul@130 834
        l = set()
paul@130 835
        for attrname in attrnames:
paul@130 836
            if attrname:
paul@130 837
                l.add(attrname)
paul@130 838
        locations.append(l)
paul@130 839
    return locations
paul@92 840
paul@92 841
# vim: tabstop=4 expandtab shiftwidth=4