1 #!/usr/bin/env python 2 3 """ 4 Generate C code from object layouts and other deduced information. 5 6 Copyright (C) 2015, 2016 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 from common import CommonOutput 23 from encoders import encode_bound_reference, encode_function_pointer, \ 24 encode_instantiator_pointer, encode_path, encode_symbol, \ 25 encode_type_attribute 26 from os import listdir 27 from os.path import isdir, join, split 28 from referencing import Reference 29 30 def copy(source, target): 31 32 "Copy a text file from 'source' to 'target'." 33 34 if isdir(target): 35 target = join(target, split(source)[-1]) 36 infile = open(source) 37 outfile = open(target, "w") 38 try: 39 outfile.write(infile.read()) 40 finally: 41 outfile.close() 42 infile.close() 43 44 class Generator(CommonOutput): 45 46 "A code generator." 47 48 function_type = "__builtins__.core.function" 49 50 table_name_prefixes = { 51 "<class>" : "Class", 52 "<module>" : "Module", 53 "<instance>" : "Instance" 54 } 55 56 structure_size_prefixes = { 57 "<class>" : "c", 58 "<module>" : "m", 59 "<instance>" : "i" 60 } 61 62 def __init__(self, importer, optimiser, output): 63 self.importer = importer 64 self.optimiser = optimiser 65 self.output = output 66 67 def to_output(self): 68 69 "Write the generated code." 70 71 self.check_output() 72 self.write_structures() 73 self.copy_templates() 74 75 def copy_templates(self): 76 77 "Copy template files to the generated output directory." 78 79 templates = join(split(__file__)[0], "templates") 80 81 for filename in listdir(templates): 82 copy(join(templates, filename), self.output) 83 84 def write_structures(self): 85 86 "Write structures used by the program." 87 88 f_consts = open(join(self.output, "progconsts.h"), "w") 89 f_defs = open(join(self.output, "progtypes.c"), "w") 90 f_decls = open(join(self.output, "progtypes.h"), "w") 91 f_signatures = open(join(self.output, "main.h"), "w") 92 f_code = open(join(self.output, "main.c"), "w") 93 94 try: 95 # Output boilerplate. 96 97 print >>f_consts, """\ 98 #ifndef __PROGCONSTS_H__ 99 #define __PROGCONSTS_H__ 100 """ 101 print >>f_decls, """\ 102 #ifndef __PROGTYPES_H__ 103 #define __PROGTYPES_H__ 104 105 #include "progconsts.h" 106 #include "types.h" 107 """ 108 print >>f_defs, """\ 109 #include "progtypes.h" 110 #include "progops.h" 111 #include "main.h" 112 """ 113 print >>f_signatures, """\ 114 #ifndef __MAIN_H__ 115 #define __MAIN_H__ 116 117 #include "types.h" 118 """ 119 print >>f_code, """\ 120 #include <string.h> 121 #include "types.h" 122 #include "ops.h" 123 #include "progconsts.h" 124 #include "progtypes.h" 125 #include "progops.h" 126 #include "main.h" 127 """ 128 129 # Generate structure size data. 130 131 size_tables = {} 132 133 for kind in ["<class>", "<module>", "<instance>"]: 134 size_tables[kind] = {} 135 136 for ref, structure in self.optimiser.structures.items(): 137 size_tables[ref.get_kind()][ref.get_origin()] = len(structure) 138 139 size_tables = size_tables.items() 140 size_tables.sort() 141 142 for kind, sizes in size_tables: 143 self.write_size_constants(f_consts, self.structure_size_prefixes[kind], sizes, 0) 144 145 # Generate parameter table size data. 146 147 min_sizes = {} 148 max_sizes = {} 149 150 for path, parameters in self.optimiser.parameters.items(): 151 argmin, argmax = self.get_argument_limits(path) 152 min_sizes[path] = argmin 153 max_sizes[path] = argmax 154 155 # Record instantiator limits. 156 157 if path.endswith(".__init__"): 158 path = path[:-len(".__init__")] 159 160 self.write_size_constants(f_consts, "pmin", min_sizes, 0) 161 self.write_size_constants(f_consts, "pmax", max_sizes, 0) 162 163 # Generate parameter codes. 164 165 self.write_code_constants(f_consts, self.optimiser.all_paramnames, self.optimiser.arg_locations, "pcode", "ppos") 166 167 # Generate attribute codes. 168 169 self.write_code_constants(f_consts, self.optimiser.all_attrnames, self.optimiser.locations, "code", "pos") 170 171 # Generate table and structure data. 172 173 function_instance_attrs = None 174 objects = self.optimiser.attr_table.items() 175 objects.sort() 176 177 for ref, indexes in objects: 178 attrnames = self.get_attribute_names(indexes) 179 180 kind = ref.get_kind() 181 path = ref.get_origin() 182 table_name = encode_tablename(self.table_name_prefixes[kind], path) 183 structure_size = encode_size(self.structure_size_prefixes[kind], path) 184 185 # Generate structures for classes and modules. 186 187 if kind != "<instance>": 188 structure = [] 189 attrs = self.get_static_attributes(kind, path, attrnames) 190 191 # Set a special instantiator on the class. 192 193 if kind == "<class>": 194 attrs["__fn__"] = path 195 attrs["__args__"] = encode_size("pmin", path) 196 197 # Write instantiator declarations based on the 198 # applicable initialiser. 199 200 init_ref = attrs["__init__"] 201 202 # Signature: __attr __new_<name>(__attr[]); 203 204 print >>f_signatures, "__attr %s(__attr[]);" % encode_instantiator_pointer(path) 205 206 # Write instantiator definitions. 207 208 self.write_instantiator(f_code, path, init_ref) 209 210 # Write parameter table. 211 212 self.make_parameter_table(f_decls, f_defs, path, init_ref.get_origin()) 213 214 self.populate_structure(Reference(kind, path), attrs, kind, structure) 215 self.write_structure(f_decls, f_defs, path, table_name, structure_size, structure) 216 217 # Record function instance details for function generation below. 218 219 else: 220 attrs = self.get_instance_attributes(path, attrnames) 221 if path == self.function_type: 222 function_instance_attrs = attrs 223 224 # Write a table for all objects. 225 226 table = [] 227 self.populate_table(Reference(kind, path), table) 228 self.write_table(f_decls, f_defs, table_name, structure_size, table) 229 230 # Generate function instances. 231 232 functions = set() 233 234 for ref in self.importer.objects.values(): 235 if ref.has_kind("<function>"): 236 functions.add(ref.get_origin()) 237 238 functions = list(functions) 239 functions.sort() 240 241 for path in functions: 242 cls = self.function_type 243 table_name = encode_tablename("Instance", cls) 244 structure_size = encode_size(self.structure_size_prefixes["<instance>"], cls) 245 246 # Set a special callable attribute on the instance. 247 248 function_instance_attrs["__fn__"] = path 249 function_instance_attrs["__args__"] = encode_size("pmin", path) 250 251 # Produce two structures where a method is involved. 252 253 ref = self.importer.get_object(path) 254 parent_ref = self.importer.get_object(ref.parent()) 255 parent_kind = parent_ref and parent_ref.get_kind() 256 257 # Populate and write each structure. 258 259 if parent_kind == "<class>": 260 261 # A bound version of a method. 262 263 structure = self.populate_function(path, function_instance_attrs, False) 264 self.write_structure(f_decls, f_defs, path, table_name, structure_size, structure, 265 encode_bound_reference(path)) 266 267 # An unbound version of a method. 268 269 structure = self.populate_function(path, function_instance_attrs, True) 270 self.write_structure(f_decls, f_defs, path, table_name, structure_size, structure) 271 272 else: 273 # A normal function. 274 275 structure = self.populate_function(path, function_instance_attrs, False) 276 self.write_structure(f_decls, f_defs, path, table_name, structure_size, structure) 277 278 # Write function declarations. 279 # Signature: __attr <name>(__attr[]); 280 281 print >>f_signatures, "__attr %s(__attr args[]);" % encode_function_pointer(path) 282 283 # Write parameter table. 284 285 self.make_parameter_table(f_decls, f_defs, path, path) 286 287 # Output more boilerplate. 288 289 print >>f_consts, """\ 290 291 #endif /* __PROGCONSTS_H__ */""" 292 293 print >>f_decls, """\ 294 295 #define __FUNCTION_TYPE %s 296 #define __FUNCTION_INSTANCE_SIZE %s 297 298 #endif /* __PROGTYPES_H__ */""" % ( 299 encode_path(self.function_type), 300 encode_size(self.structure_size_prefixes["<instance>"], self.function_type) 301 ) 302 303 print >>f_signatures, """\ 304 305 #endif /* __MAIN_H__ */""" 306 307 finally: 308 f_consts.close() 309 f_defs.close() 310 f_decls.close() 311 f_signatures.close() 312 f_code.close() 313 314 def make_parameter_table(self, f_decls, f_defs, path, function_path): 315 316 """ 317 Write parameter table details to 'f_decls' (to declare a table) and to 318 'f_defs' (to define the contents) for the function with the given 319 'path', using 'function_path' to obtain the parameter details. The 320 latter two arguments may differ when describing an instantiator using 321 the details of an initialiser. 322 """ 323 324 table = [] 325 table_name = encode_tablename("Function", path) 326 structure_size = encode_size("pmax", path) 327 self.populate_parameter_table(function_path, table) 328 self.write_parameter_table(f_decls, f_defs, table_name, structure_size, table) 329 330 def write_size_constants(self, f_consts, size_prefix, sizes, padding): 331 332 """ 333 Write size constants to 'f_consts' for the given 'size_prefix', using 334 the 'sizes' dictionary to populate the definition, adding the given 335 'padding' to the basic sizes. 336 """ 337 338 print >>f_consts, "enum %s {" % encode_size(size_prefix) 339 first = True 340 for path, size in sizes.items(): 341 if not first: 342 print >>f_consts, "," 343 else: 344 first = False 345 f_consts.write(" %s = %d" % (encode_size(size_prefix, path), size + padding)) 346 print >>f_consts, "\n };" 347 348 def write_code_constants(self, f_consts, attrnames, locations, code_prefix, pos_prefix): 349 350 """ 351 Write code constants to 'f_consts' for the given 'attrnames' and 352 attribute 'locations'. 353 """ 354 355 print >>f_consts, "enum %s {" % encode_symbol(code_prefix) 356 first = True 357 for i, attrname in enumerate(attrnames): 358 if not first: 359 print >>f_consts, "," 360 else: 361 first = False 362 f_consts.write(" %s = %d" % (encode_symbol(code_prefix, attrname), i)) 363 print >>f_consts, "\n };" 364 365 print >>f_consts, "enum %s {" % encode_symbol(pos_prefix) 366 first = True 367 for i, attrnames in enumerate(locations): 368 for attrname in attrnames: 369 if not first: 370 print >>f_consts, "," 371 else: 372 first = False 373 f_consts.write(" %s = %d" % (encode_symbol(pos_prefix, attrname), i)) 374 print >>f_consts, "\n };" 375 376 def write_table(self, f_decls, f_defs, table_name, structure_size, table): 377 378 """ 379 Write the declarations to 'f_decls' and definitions to 'f_defs' for 380 the object having the given 'table_name' and the given 'structure_size', 381 with 'table' details used to populate the definition. 382 """ 383 384 print >>f_decls, "extern const __table %s;\n" % table_name 385 386 # Write the corresponding definition. 387 388 print >>f_defs, "const __table %s = {\n %s,\n {\n %s\n }\n };\n" % ( 389 table_name, structure_size, 390 ",\n ".join(table)) 391 392 def write_parameter_table(self, f_decls, f_defs, table_name, structure_size, table): 393 394 """ 395 Write the declarations to 'f_decls' and definitions to 'f_defs' for 396 the object having the given 'table_name' and the given 'structure_size', 397 with 'table' details used to populate the definition. 398 """ 399 400 print >>f_decls, "extern const __ptable %s;\n" % table_name 401 402 # Write the corresponding definition. 403 404 print >>f_defs, "const __ptable %s = {\n %s,\n {\n %s\n }\n };\n" % ( 405 table_name, structure_size, 406 ",\n ".join([("{%s, %s}" % t) for t in table])) 407 408 def write_structure(self, f_decls, f_defs, path, table_name, structure_size, structure, copy=None): 409 410 """ 411 Write the declarations to 'f_decls' and definitions to 'f_defs' for 412 the object having the given 'path', the given 'table_name', and the 413 given 'structure_size', with 'structure' details used to populate the 414 definition. 415 """ 416 417 print >>f_decls, "extern __obj %s;\n" % encode_path(path) 418 419 # Write an instance-specific type definition for instances of classes. 420 # See: templates/types.h 421 422 print >>f_decls, """\ 423 typedef struct { 424 const __table * table; 425 unsigned int pos; 426 __attr attrs[%s]; 427 } %s; 428 """ % (structure_size, encode_symbol("obj", copy or path)) 429 430 # Write the corresponding definition. 431 432 is_class = self.importer.get_object(path).has_kind("<class>") 433 pos = is_class and encode_symbol("pos", encode_type_attribute(path)) or "0" 434 435 print >>f_defs, """\ 436 __obj %s = { 437 &%s, 438 %s, 439 { 440 %s 441 }}; 442 """ % ( 443 encode_path(copy or path), table_name, pos, 444 ",\n ".join(structure)) 445 446 def get_argument_limits(self, path): 447 448 """ 449 Return the argument minimum and maximum for the callable at 'path', 450 adding an argument position for a universal context. 451 """ 452 453 parameters = self.importer.function_parameters[path] 454 defaults = self.importer.function_defaults.get(path) 455 num_parameters = len(parameters) + 1 456 return num_parameters - (defaults and len(defaults) or 0), num_parameters 457 458 def get_attribute_names(self, indexes): 459 460 """ 461 Given a list of attribute table 'indexes', return a list of attribute 462 names. 463 """ 464 465 all_attrnames = self.optimiser.all_attrnames 466 attrnames = [] 467 for i in indexes: 468 if i is None: 469 attrnames.append(None) 470 else: 471 attrnames.append(all_attrnames[i]) 472 return attrnames 473 474 def get_static_attributes(self, kind, name, attrnames): 475 476 """ 477 Return a mapping of attribute names to paths for attributes belonging 478 to objects of the given 'kind' (being "<class>" or "<module>") with 479 the given 'name' and supporting the given 'attrnames'. 480 """ 481 482 attrs = {} 483 484 for attrname in attrnames: 485 if attrname is None: 486 continue 487 if kind == "<class>": 488 path = self.importer.all_class_attrs[name][attrname] 489 elif kind == "<module>": 490 path = "%s.%s" % (name, attrname) 491 else: 492 continue 493 494 # The module may be hidden. 495 496 attr = self.importer.get_object(path) 497 if not attr: 498 module = self.importer.hidden.get(path) 499 if module: 500 attr = Reference(module.name, "<module>") 501 attrs[attrname] = attr 502 503 return attrs 504 505 def get_instance_attributes(self, name, attrnames): 506 507 """ 508 Return a mapping of attribute names to references for attributes 509 belonging to instances of the class with the given 'name', where the 510 given 'attrnames' are supported. 511 """ 512 513 consts = self.importer.all_instance_attr_constants[name] 514 attrs = {} 515 for attrname in attrnames: 516 if attrname is None: 517 continue 518 const = consts.get(attrname) 519 attrs[attrname] = const or Reference("<var>", "%s.%s" % (name, attrname)) 520 return attrs 521 522 def populate_table(self, key, table): 523 524 """ 525 Traverse the attributes in the determined order for the structure having 526 the given 'key', adding entries to the attribute 'table'. 527 """ 528 529 for attrname in self.optimiser.structures[key]: 530 531 # Handle gaps in the structure. 532 533 if attrname is None: 534 table.append("0") 535 else: 536 table.append(encode_symbol("code", attrname)) 537 538 def populate_parameter_table(self, key, table): 539 540 """ 541 Traverse the parameters in the determined order for the structure having 542 the given 'key', adding entries to the attribute 'table'. 543 """ 544 545 for value in self.optimiser.parameters[key]: 546 547 # Handle gaps in the structure. 548 549 if value is None: 550 table.append(("0", "0")) 551 else: 552 name, pos = value 553 table.append((encode_symbol("pcode", name), pos)) 554 555 def populate_function(self, path, function_instance_attrs, unbound=False): 556 557 """ 558 Populate a structure for the function with the given 'path'. The given 559 'attrs' provide the instance attributes, and if 'unbound' is set to a 560 true value, an unbound method structure is produced (as opposed to a 561 callable bound method structure). 562 """ 563 564 cls = self.function_type 565 structure = [] 566 self.populate_structure(Reference("<instance>", cls), function_instance_attrs, "<instance>", structure, unbound) 567 568 # Append default members. 569 570 self.append_defaults(path, structure) 571 return structure 572 573 def populate_structure(self, ref, attrs, kind, structure, unbound=False): 574 575 """ 576 Traverse the attributes in the determined order for the structure having 577 the given 'ref' whose members are provided by the 'attrs' mapping, in a 578 structure of the given 'kind', adding entries to the object 'structure'. 579 If 'unbound' is set to a true value, an unbound method function pointer 580 will be employed, with a reference to the bound method incorporated into 581 the special __fn__ attribute. 582 """ 583 584 origin = ref.get_origin() 585 586 for attrname in self.optimiser.structures[ref]: 587 588 # Handle gaps in the structure. 589 590 if attrname is None: 591 structure.append("{0, 0}") 592 593 # Handle non-constant and constant members. 594 595 else: 596 attr = attrs[attrname] 597 598 if attrname == "__fn__": 599 600 # Provide bound method references and the unbound function 601 # pointer if populating methods in a class. 602 603 bound_attr = None 604 605 # Classes offer instantiators. 606 607 if kind == "<class>": 608 attr = encode_instantiator_pointer(attr) 609 610 # Methods offers references to bound versions and an unbound 611 # method function. 612 613 elif unbound: 614 bound_attr = encode_bound_reference(attr) 615 attr = "__unbound_method" 616 617 # Other functions just offer function pointers. 618 619 else: 620 attr = encode_function_pointer(attr) 621 622 structure.append("{%s, .fn=%s}" % (bound_attr and ".b=&%s" % bound_attr or "0", attr)) 623 continue 624 625 elif attrname == "__args__": 626 structure.append("{.min=%s, .ptable=&%s}" % (attr, encode_tablename("Function", origin))) 627 continue 628 629 structure.append(self.encode_member(origin, attrname, attr, kind)) 630 631 def encode_member(self, path, name, ref, structure_type): 632 633 """ 634 Encode within the structure provided by 'path', the member whose 'name' 635 provides 'ref', within the given 'structure_type'. 636 """ 637 638 kind = ref.get_kind() 639 origin = ref.get_origin() 640 641 # References to constant literals. 642 643 if kind == "<instance>": 644 attr_path = "%s.%s" % (path, name) 645 646 # Obtain a constant value directly assigned to the attribute. 647 648 if self.optimiser.constant_numbers.has_key(attr_path): 649 constant_number = self.optimiser.constant_numbers[attr_path] 650 constant_value = "const%d" % constant_number 651 return "{&%s, &%s} /* %s */" % (constant_value, constant_value, name) 652 653 # General undetermined members. 654 655 if kind in ("<var>", "<instance>"): 656 return "{0, 0} /* %s */" % name 657 658 # Set the context depending on the kind of attribute. 659 # For methods: {&<path>, &<attr>} 660 # For other attributes: {&<attr>, &<attr>} 661 662 else: 663 context = (kind == "<function>" and structure_type == "<class>" and \ 664 "&%s" % encode_path(path) or "0") or \ 665 kind == "<instance>" and "&%s" % encode_path(origin) or "0" 666 return "{%s, &%s}" % (context, encode_path(origin)) 667 668 def append_defaults(self, path, structure): 669 670 """ 671 For the given 'path', append default parameter members to the given 672 'structure'. 673 """ 674 675 for name, default in self.importer.function_defaults.get(path): 676 structure.append(self.encode_member(path, name, default, "<instance>")) 677 678 def write_instantiator(self, f_code, path, init_ref): 679 680 """ 681 Write an instantiator to 'f_code' for instances of the class with the 682 given 'path', with 'init_ref' as the initialiser function reference. 683 684 NOTE: This also needs to initialise any __fn__ and __args__ members 685 NOTE: where __call__ is provided by the class. 686 """ 687 688 parameters = self.importer.function_parameters[init_ref.get_origin()] 689 690 print >>f_code, """\ 691 __attr %s(__attr __args[]) 692 { 693 __args[0] = __new(&%s, &%s, sizeof(%s)); 694 %s 695 return __args[0]; 696 } 697 """ % ( 698 encode_instantiator_pointer(path), 699 encode_tablename("Instance", path), encode_path(path), encode_symbol("obj", path), 700 encode_function_pointer(init_ref.get_origin()) 701 ) 702 703 def encode_size(table_type, path=None): 704 return "__%ssize%s" % (table_type, path and "_%s" % encode_path(path) or "") 705 706 def encode_tablename(table_type, path): 707 return "__%sTable_%s" % (table_type, encode_path(path)) 708 709 # vim: tabstop=4 expandtab shiftwidth=4