Lichen

Annotated optimiser.py

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