Lichen

Annotated optimiser.py

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