Merge remote-tracking branch 'redux/master' into sh4-pool
[tamarin-stm.git] / utils / exactgc.as
blob3edc48c81c2460ca93ad69c56cec9eeb7f3b2aa1
1 /* -*- indent-tabs-mode: nil; tab-width: 4 -*- */
2 /* vi: set ts=4 sw=4 expandtab: (add to ~/.vimrc: set modeline modelines=5) */
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is [Open Source Virtual Machine.].
18 * The Initial Developer of the Original Code is
19 * Adobe System Incorporated.
20 * Portions created by the Initial Developer are Copyright (C) 2010
21 * the Initial Developer. All Rights Reserved.
23 * Contributor(s):
24 * Adobe AS3 Team
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 // exactgc.as generates exact tracing code from annotated classes. It
41 // is usually run by builtin-tracers.py, shell_toplevel-tracers.py,
42 // and similar scripts, and occasionally cooperates with nativegen.py.
44 // The <a href="doc/mmgc/exactgc-cookbook.html">cookbook</a> provides a gentle
45 // introduction to annotations; most people need look no further.
47 // The <a href="doc/mmgc/exactgc-manual.html">manual</a> provides a complete
48 // reference manual for exact GC and describes every facility provided
49 // by exactgc.as and more.
52 // Usage & options.
54 // exactgc.as extracts native attributes from ".as" and ".h" files and
55 // generates C++ code. Little is assumed about the structure of the
56 // files; indeed the script looks only for annotations, never for C++
57 // or AS3 phrases, and you can blindly feed a lot of files to it and
58 // expect the right thing to happen.
60 // Typical usage:
62 // avm exactgc.abc -- -b avmplus-as3-gc.h -n avmplus-cpp-gc.h -i avmplus-gc-interlock.h *.h *.as
63 //
64 // Options:
66 // filename A file to process
67 // @filename A file from which to read more file names
68 // -x filename A file to process (useful if the filename starts with '@' or '-')
69 // -n filename Emit tracers for GC_CPP_EXACT ("natives") to this file
70 // -b filename Emit tracers for GC_AS3_EXACT ("builtins") to this file
71 // -i filename Emit interlock definitons to this file
72 // -ns namespace The C++ namespace to wrap around the output, default global
75 // Performance notes.
77 // The script has been tuned quite a bit, and there's a built-in profiling option
78 // to help us tune more (search for 'var profiling' below). Even so, about
79 // 90% of the time is still spent in reading input and extracting annotations, so
80 // obvious things to do if further performance improvements are needed would be:
82 // - cache intermediate data (eg, save extracted lines + file's path and mtime)
83 // - don't split the file into individual lines (splitting takes some time).
85 // The design is currently line-based because I would like to expand it later to
86 // include various kinds of error checking that goes hand-in-hand with SafeGC, eg,
87 // if GCMember appears on a line then there should be a GC annotation too. So
88 // it would be good to try caching first.
90 import avmplus.*;
92 // For [native] annotations on AS3 class definitions that have "gc",
93 // "clsgc", or "instancegc" annotations.
94 class AS3Class
96 function AS3Class(ns, cls) {
97 // This is for dealing with the "::avmshell::" prefix in some native annotations,
98 // though there is probably a better way.
99 var s = "::" + ns + "::";
100 if (cls.indexOf(s) == 0)
101 cls = cls.substring(s.length);
102 this.cls = cls;
105 public function toString() { return printProps(this, ["cls"]) }
107 const cls;
111 // For GC_AS3_EXACT and GC_CPP_EXACT annotations on C++ class
112 // definitions, also for the _WITH_HOOK variants. Note
113 // _WITH_HOOK_{IF,IFDEF,IFNDEF} means we can have both hooks
114 // and conditional compilation.
116 class GCClass
118 function GCClass(tag, attr) {
119 cls = getAttr(attr, 0);
120 base = getAttr(attr, 1);
121 hook = tag.match("_WITH_HOOK") != null;
122 ifdef = tag.match("_IFDEF") != null ? getAttr(attr,2) : false;
123 ifndef = tag.match("_IFNDEF") != null ? getAttr(attr,2) : false;
124 if_ = !ifdef && !ifndef && tag.match("_IF") != null ? getAttr(attr,2) : false;
127 public function toString() { return printProps(this, ["cls","base","hook","ifdef"]) }
129 public function fullName() { return fullClassPrefix + cls; }
131 const cls;
132 const base;
133 const hook;
134 const ifdef;
135 const ifndef;
136 const if_;
138 var fullClassPrefix=""; // for nested classes
139 var out = new Printer(1);
140 var next = null;
141 var fieldList = []; // sorted by property name
142 var fieldMap = {};
143 var variable_length_field = null; // does not appear in the 'fields' list
144 var probablyLarge = false;
145 var hint = null;
148 class GCCppExact extends GCClass { function GCCppExact(tag,attr) { super(tag,attr) } }
149 class GCAS3Exact extends GCClass { function GCAS3Exact(tag,attr) { super(tag,attr) } }
152 // For GC_DATA_BEGIN and GC_DATA_END annotations in C++ classes.
153 class GCDataSection
155 function GCDataSection(tag, attr) {
156 cls = getAttr(attr, 0);
159 public function toString() { return printProps(this, ["cls"]) }
161 const cls;
164 class GCDataBegin extends GCDataSection { function GCDataBegin(tag, attr) { super(tag,attr) } }
165 class GCDataEnd extends GCDataSection { function GCDataEnd(tag, attr) { super(tag,attr) } }
166 class GCNoData extends GCDataSection { function GCNoData(tag, attr) { super(tag,attr) } }
168 // For the various field annotations in C++ classes.
170 // The cls attribute is implicitly present in the C++ source code and
171 // is inserted at the beginning of the attribute array by the code
172 // that constructs GCField instances.
174 // Any condition on the field is considered part of the field's name,
175 // to support typical cases like these:
177 // #ifdef BLAH
178 // Bletch* GC_POINTER_IFDEF(p, BLAH)
179 // #else
180 // Blotch* GC_POINTER_IFNDEF(p, BLAH)
181 // #endif
183 class GCField
185 function GCField(tag, attr) {
186 cls = getAttr(attr, 0);
187 name = getAttr(attr, 1);
188 ifdef = tag.match("_IFDEF") != null ? getAttr(attr,2) : false;
189 ifndef = tag.match("_IFNDEF") != null ? getAttr(attr,2) : false;
190 if_ = !ifdef && !ifndef && tag.match("_IF") != null ? getAttr(attr,2) : false;
193 public function toString() { return printProps(this, ["cls","name","ifdef", "if_"]) }
195 const cls;
196 const name;
197 const ifdef;
198 const ifndef;
199 const if_;
202 class GCPointer extends GCField { function GCPointer(tag, attr) { super(tag, attr) } }
203 class GCAtom extends GCField { function GCAtom(tag, attr) { super(tag, attr) } }
204 class GCStructure extends GCField { function GCStructure(tag, attr) { super(tag, attr) } }
205 class GCConservative extends GCField { function GCConservative(tag, attr) { super(tag, attr) } }
207 class GCPointers extends GCField
209 function GCPointers(tag, attr) {
210 splitFieldAndSize(attr, 1);
211 super(tag,attr);
212 declCount = getAttr(attr, 2);
213 count = getAttr(attr, 3);
214 hint = tag.match("_SMALL") != null ? "small" : null;
217 override public function toString() { return printProps(this, ["cls","name","ifdef","hint"]) }
219 const declCount;
220 const count;
221 const hint;
224 class GCAtoms extends GCField
226 function GCAtoms(tag, attr) {
227 splitFieldAndSize(attr, 1);
228 super(tag,attr);
229 declCount = getAttr(attr, 2);
230 count = getAttr(attr, 3);
231 hint = tag.match("_SMALL") != null ? "small" : null;
234 override public function toString() { return printProps(this, ["cls","name","ifdef","hint"]) }
236 const declCount;
237 const count;
238 const hint;
241 class GCStructures extends GCField
243 function GCStructures(tag, attr) {
244 splitFieldAndSize(attr, 1);
245 super(tag,attr);
246 declCount = getAttr(attr, 2);
247 count = getAttr(attr, 3);
248 hint = tag.match("_SMALL") != null ? "small" : null;
251 override public function toString() { return printProps(this, ["cls","name","ifdef","hint"]) }
253 const declCount;
254 const count;
255 const hint;
258 class Printer
260 function Printer(_indent=0) {
261 for ( var i=0 ; i < _indent ; i++ )
262 IN();
265 function IN() {
266 indent+=4;
267 if (indent > indentString.length)
268 indentString += " ";
269 return this;
272 function OUT() {
273 indent-=4;
274 return this;
277 function PR(s) {
278 if (s == null)
279 return;
280 if (s.charAt(0) != "#")
281 output += indentString.substring(0,indent);
282 while (s.length > 0 && s.charAt(s.length-1) == "\n")
283 s = s.substring(0,s.length-1);
284 output += s;
285 output += "\n";
286 return this;
289 function DO(f) {
290 f(this);
291 return this;
294 function NL() {
295 return PR("");
298 function get() {
299 return output;
302 var indent = 0;
303 var indentString = "";
304 var output = "";
307 const constructors =
308 { "GC_CPP_EXACT": GCCppExact,
309 "GC_CPP_EXACT_IFDEF": GCCppExact,
310 "GC_CPP_EXACT_WITH_HOOK": GCCppExact,
311 "GC_CPP_EXACT_WITH_HOOK_IFDEF": GCCppExact,
312 "GC_CPP_EXACT_WITH_HOOK_IFNDEF": GCCppExact,
313 "GC_CPP_EXACT_WITH_HOOK_IF": GCCppExact,
314 "GC_AS3_EXACT": GCAS3Exact,
315 "GC_AS3_EXACT_IFDEF": GCAS3Exact,
316 "GC_AS3_EXACT_WITH_HOOK": GCAS3Exact,
317 "GC_AS3_EXACT_WITH_HOOK_IFDEF": GCAS3Exact,
318 "GC_AS3_EXACT_WITH_HOOK_IFNDEF": GCAS3Exact,
319 "GC_AS3_EXACT_WITH_HOOK_IF": GCAS3Exact,
320 "GC_DATA_BEGIN": GCDataBegin,
321 "GC_DATA_END": GCDataEnd,
322 "GC_NO_DATA": GCNoData,
323 "GC_ATOM": GCAtom,
324 "GC_ATOM_IF": GCAtom,
325 "GC_ATOM_IFDEF": GCAtom,
326 "GC_ATOM_IFNDEF": GCAtom,
327 "GC_POINTER": GCPointer,
328 "GC_POINTER_IF": GCPointer,
329 "GC_POINTER_IFDEF": GCPointer,
330 "GC_POINTER_IFNDEF": GCPointer,
331 "GC_CONSERVATIVE": GCConservative,
332 "GC_CONSERVATIVE_IF": GCConservative,
333 "GC_CONSERVATIVE_IFDEF": GCConservative,
334 "GC_CONSERVATIVE_IFNDEF": GCConservative,
335 "GC_STRUCTURE": GCStructure,
336 "GC_STRUCTURE_IF": GCStructure,
337 "GC_STRUCTURE_IFDEF": GCStructure,
338 "GC_STRUCTURE_IFNDEF": GCStructure,
339 "GC_ATOMS": GCAtoms,
340 "GC_ATOMS_SMALL": GCAtoms,
341 "GC_POINTERS": GCPointers,
342 "GC_POINTERS_SMALL": GCPointers,
343 "GC_STRUCTURES": GCStructures,
344 "GC_STRUCTURES_SMALL": GCStructures
347 // Configuration etc
348 var debug = false; // print useful debugging info
349 var profiling = false; // profile at a function level for selected functions
350 var errorContext = "top level"; // for error messages, updated as we go
351 const largeObjectCutoff = 2000; // more arbitrary than not, "close" to large object limit in MMgc
353 // Options
354 var builtinOutputFile = null; // null == don't output, otherwise file name
355 var nativeOutputFile = null; // null == don't output, otherwise file name
356 var interlockOutputFile = null; // null == don't output (and don't do interlock checking), otherwise file name
357 var cppNamespace = ""; // wrap this namespace around the emitted code if not empty string
359 // Used during parsing and some initial processing
360 var specs = []; // intermediate list of all metadata during parsing and field/class collection
362 // Created during processing
363 var cppClassList = []; // all C++ classes ordered by class name
364 var as3ClassList = []; // all AS3 classes ordered by class name
365 var cppClassMap = {}; // maps C++ class name to GCClass
366 var as3ClassMap = {}; // maps AS3 class name to GCClass
368 // Occasionally useful during debugging to print the attribute maps
369 Object.prototype.toString =
370 function() {
371 var s=[];
372 for ( var i in this )
373 if (this.hasOwnProperty(i))
374 s.push(i + ": " + this[i]);
375 return "{" + s.join(", ") + "}";
378 Array.prototype.toString =
379 function() {
380 return "[" + this.join(",") + "]";
383 function printProps(obj, attrs) {
384 var s = [];
385 for ( var i=0 ; i < attrs.length ; i++ )
386 s.push(attrs[i] + ": " + obj[attrs[i]]);
387 return "{" + s.join(", ") + "}";
390 const LICENSE =
391 ("/* ***** BEGIN LICENSE BLOCK *****\n" +
392 " * Version: MPL 1.1/GPL 2.0/LGPL 2.1\n" +
393 " *\n" +
394 " * The contents of this file are subject to the Mozilla Public License Version\n" +
395 " * 1.1 (the \"License\"); you may not use this file except in compliance with\n" +
396 " * the License. You may obtain a copy of the License at\n" +
397 " * http://www.mozilla.org/MPL/\n" +
398 " *\n" +
399 " * Software distributed under the License is distributed on an \"AS IS\" basis,\n" +
400 " * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License\n" +
401 " * for the specific language governing rights and limitations under the\n" +
402 " * License.\n" +
403 " *\n" +
404 " * The Original Code is [Open Source Virtual Machine.].\n" +
405 " *\n" +
406 " * The Initial Developer of the Original Code is\n" +
407 " * Adobe System Incorporated.\n" +
408 " * Portions created by the Initial Developer are Copyright (C) 2010\n" +
409 " * the Initial Developer. All Rights Reserved.\n" +
410 " *\n" +
411 " * Contributor(s):\n" +
412 " * Adobe AS3 Team\n" +
413 " *\n" +
414 " * Alternatively, the contents of this file may be used under the terms of\n" +
415 " * either the GNU General Public License Version 2 or later (the \"GPL\"), or\n" +
416 " * the GNU Lesser General Public License Version 2.1 or later (the \"LGPL\"),\n" +
417 " * in which case the provisions of the GPL or the LGPL are applicable instead\n" +
418 " * of those above. If you wish to allow use of your version of this file only\n" +
419 " * under the terms of either the GPL or the LGPL, and not to allow others to\n" +
420 " * use your version of this file under the terms of the MPL, indicate your\n" +
421 " * decision by deleting the provisions above and replace them with the notice\n" +
422 " * and other provisions required by the GPL or the LGPL. If you do not delete\n" +
423 " * the provisions above, a recipient may use your version of this file under\n" +
424 " * the terms of any one of the MPL, the GPL or the LGPL.\n" +
425 " *\n" +
426 " * ***** END LICENSE BLOCK ***** */\n");
428 // Read an attribute with range checking from the attribute array
429 function getAttr(attr, n)
431 if (n >= attr.length)
432 fail("Out-of-range attribute reference: " + attr + " " + n);
433 return attr[n];
436 // attr[n] has text of the form "<something1>[<something2>]" with no embedded commas.
437 // Break them apart, and leave <something1> in position n and insert <something2> in
438 // position n+1.
440 function splitFieldAndSize(attr, n)
442 if (n >= attr.length)
443 fail("Out-of-range attribute reference: " + attr + " " + n);
444 var v = attr[n];
445 var result = (/^\s*([^\]]*)\s*\[([^\]]*)\]\s*$/).exec(v);
446 if (result == null || result.length != 3)
447 fail("Incorrect format for array field, expected name[size]: " + n);
448 for ( var k=attr.length ; k > n+1 ; k-- )
449 attr[k] = attr[k-1];
450 attr[n] = result[1];
451 attr[n+1] = result[2];
454 // Fail with an error message, never return
455 function fail(s)
457 throw new Error(errorContext + ": " + s);
460 // Compare two strings and return -1, 0, or 1
461 function strcmp(a,b)
463 if (a < b) return -1;
464 if (b < a) return 1;
465 return 0;
468 // The following function is about 20x faster than the obvious one-liner
469 // return File.read(filename).split(/\r\n|\r|\n/);
471 // Using single regular expressions in place of the disjunction gave us
472 // a factor of five; switching to strings for the splitting another
473 // factor of four. (Reorganizing the code further, to use knowledge
474 // of the absence of \r to avoid scanning for \r\n, say, has yielded
475 // nothing.)
477 function readLines(filename) {
478 var text = File.read(filename);
479 if (text.indexOf("\r\n") != -1)
480 return text.split("\r\n");
482 if (text.indexOf("\r") != -1)
483 return text.split("\r");
485 return text.split("\n");
488 // Look for and record any options, return array of file names. Skip
489 // files named 'GC.h' because MMgc/GC.h includes the macros for the
490 // annotations and cannot be processed.
491 function processOptionsAndFindFiles(args)
493 var files = [];
495 errorContext = "Processing options";
496 processArgs(args);
497 return files;
499 function processArgs(args) {
500 var i=0;
501 var limit=args.length;
503 function strip(s) {
504 var result = (/^\s*(.*)\s*$/).exec(s);
505 if (result == null) // shouldn't happen
506 return s;
507 return result[1];
510 function getArg(opt) {
511 if (i == limit)
512 fail("Missing argument for " + opt);
513 return args[i++];
516 while (i < limit) {
517 var s = args[i++];
518 if (s == "-b")
519 builtinOutputFile = getArg(s);
520 else if (s == "-n")
521 nativeOutputFile = getArg(s);
522 else if (s == "-i")
523 interlockOutputFile = getArg(s);
524 else if (s == "-ns")
525 cppNamespace = getArg(s);
526 else if (s == "-x")
527 files.push(getArg(s));
528 else if (s.match(/GC\.h$/))
530 else if (s.charAt(0) == "@") // indirection file
531 files.push.apply(files, readLines(s.substring(1)).map(strip));
532 else
533 files.push(s);
538 // Read files, accumulate all metadata in order in 'specs'.
540 // Syntactic side conditions:
542 // GC_DATA_BEGIN must follow a GC class spec with the same name.
543 // GC_DATA_END pops both stacks, so there can only be one data
544 // section per class.
546 // At the end of a file, both stacks must be empty.
548 function readFiles(files)
550 // For profiling
551 var readingTime = 0;
552 var processingTime = 0;
553 var splittingTime = 0;
555 var cppClassStack = []; // tracks GC_CPP_EXACT, etc
556 var cppDataStack = []; // tracks GC_DATA_BEGIN / GC_DATA_END
558 // TODO: factor regular expressions to avoid duplication?
560 const attrStringRegex:RegExp = /^\s*\"([^\"]*)\"\s*$/;
561 const attrMiscRegex:RegExp = /^\s*((?:<\s+|\s+>|[a-zA-Z0-9_:<>])+)\s*$/;
562 const attrNumberRegex:RegExp = /^\s*([0-9]+(?:\.[0-9]+)?)\s*$/;
563 const attrArraydefRegex:RegExp = /^\s*([a-zA-Z0-9_]+\[[^\]]*\])\s*$/;
565 function parseAttrValue(s) {
566 var result;
567 if ((result = attrStringRegex.exec(s)) != null ||
568 (result = attrMiscRegex.exec(s)) != null ||
569 (result = attrNumberRegex.exec(s)) != null ||
570 (result = attrArraydefRegex.exec(s)) != null) {
571 return result[1];
573 else
574 fail("Bogus name-value pair: " + s);
577 // We can't simply split by "," here because the value can
578 // contain a comma, for example, in "count" attributes the
579 // expression frequently uses offsetof(a,b).
581 const spacesStartRegex:RegExp = /^(\s+)/;
582 const spacesEndRegex:RegExp = /(\s+)$/;
583 const commaSpacesStartRegex:RegExp = /^(,\s*)/;
584 const nameValuePairRegex:RegExp = /^([a-zA-Z0-9_]+)\s*=\s*(\"[^\"]*\"|true|false)/;
585 const valueRegex:RegExp = /^\"[^\"]*\"|[a-zA-Z0-9_]+\[[^\]]*\]|(?:[0-9]+(?:\.[0-9]+)?)|(?:<\s+|\s+>|[a-zA-Z0-9_:<>])+/;
587 function splitAttrs(s, paren=null) {
588 var then = new Date();
589 var xs = [];
590 var result;
591 for (;;) {
592 // strip leading and trailing spaces
593 if ((result = spacesStartRegex.exec(s)) != null)
594 s = s.substring(result[1].length);
595 if ((result = spacesEndRegex.exec(s)) != null)
596 s = s.substring(0,s.length-result[1].length);
598 if (paren != null) {
599 if (s == "")
600 fail("Missing closing parenthesis: '" + paren + "'");
601 if (s.charAt(0) == paren)
602 break;
604 else if (s == "")
605 break;
607 // strip leading comma and any spaces following it
608 if (xs.length > 0) {
609 if ((result = commaSpacesStartRegex.exec(s)) == null)
610 fail("Incorrect attribute string: missing comma in " + s);
611 s = s.substring(result[1].length);
613 if (s == "")
614 break;
615 // simple-identifier=(string|boolean), string, number, qualified-identifier
616 if ((result = nameValuePairRegex.exec(s)) != null)
617 xs.push([result[1], parseAttrValue(result[2])]);
618 else if ((result = valueRegex.exec(s)) != null)
619 xs.push(parseAttrValue(result[0]));
620 else
621 fail("Incorrect attribute string: bad value or name/value pair: " + s);
622 s = s.substring(result[0].length)
624 if (debug)
625 print(xs);
626 splittingTime += (new Date() - then);
627 return xs;
630 function positionalAttrs(tag, s, paren, cls) {
631 var attr = splitAttrs(s, paren);
632 for ( var i=0 ; i < attr.length ; i++ ) {
633 if (attr[i] is Array)
634 fail("Named attributes not allowed here: " + attr[i]);
636 if (cls != null)
637 attr.unshift(cls);
638 return new constructors[tag](tag,attr);
641 function parseNamedAttrs(s) {
642 var attr = splitAttrs(s);
643 for ( var i=0 ; i < attr.length ; i++ ) {
644 if (!(attr[i] is Array || i ==0 && attr[i] is String && attr.length == 1))
645 fail("Named attributes required here: " + attr[i]);
647 return attr;
650 function reportMatch(line) {
651 if (debug)
652 print(line);
655 function currentClassName() {
656 if (cppDataStack.length == 0)
657 fail("No active GC_DATA_BEGIN");
658 return cppDataStack[cppDataStack.length-1];
661 // Does not match the trailing right paren. $1 is the tag, $2 the
662 // rest of the text starting just right of the left paren for the
663 // argument list.
665 const cppMetaTag =
666 new RegExp("^(" +
667 ["GC_CPP_EXACT_WITH_HOOK_IFDEF",
668 "GC_CPP_EXACT_WITH_HOOK_IFNDEF",
669 "GC_CPP_EXACT_WITH_HOOK_IF",
670 "GC_CPP_EXACT_WITH_HOOK",
671 "GC_CPP_EXACT_WITH_HOOK",
672 "GC_CPP_EXACT_WITH_HOOK",
673 "GC_CPP_EXACT_IFDEF",
674 "GC_CPP_EXACT_IFNDEF",
675 "GC_CPP_EXACT_IF",
676 "GC_CPP_EXACT",
677 "GC_AS3_EXACT_WITH_HOOK_IFDEF",
678 "GC_AS3_EXACT_WITH_HOOK_IFNDEF",
679 "GC_AS3_EXACT_WITH_HOOK_IF",
680 "GC_AS3_EXACT_WITH_HOOK",
681 "GC_AS3_EXACT_WITH_HOOK",
682 "GC_AS3_EXACT_WITH_HOOK",
683 "GC_AS3_EXACT_IFDEF",
684 "GC_AS3_EXACT_IFNDEF",
685 "GC_AS3_EXACT_IF",
686 "GC_AS3_EXACT",
687 "GC_NO_DATA",
688 "GC_DATA_BEGIN",
689 "GC_DATA_END"].join("|") +
690 ")\\s*\\((.*)");
692 function matchCppMetaTag(line, where) {
693 return cppMetaTag.exec(line.substring(where));
696 // Does not match the trailing right paren. $1 is the tag, $2 the
697 // rest of the text starting just right of the left paren for the
698 // argument list.
700 const cppFieldTag =
701 new RegExp("^(" +
702 ["GC_POINTERS",
703 "GC_POINTERS_SMALL",
704 "GC_STRUCTURES",
705 "GC_STRUCTURES_SMALL",
706 "GC_ATOMS",
707 "GC_ATOMS_SMALL",
708 "GC_STRUCTURE",
709 "GC_STRUCTURE_IFDEF",
710 "GC_STRUCTURE_IFNDEF",
711 "GC_STRUCTURE_IF",
712 "GC_POINTER",
713 "GC_POINTER_IFDEF",
714 "GC_POINTER_IFNDEF",
715 "GC_POINTER_IF",
716 "GC_ATOM",
717 "GC_ATOM_IFDEF",
718 "GC_ATOM_IFNDEF",
719 "GC_ATOM_IF",
720 "GC_CONSERVATIVE",
721 "GC_CONSERVATIVE_IFDEF",
722 "GC_CONSERVATIVE_IFNDEF",
723 "GC_CONSERVATIVE_IF"].join("|") +
724 ")\\s*\\((.*)");
726 function matchCppFieldTag(line, where) {
727 return cppFieldTag.exec(line.substring(where));
730 function stackToCppPrefix() {
731 var pfx="";
732 for ( var i=0; i < cppClassStack.length; i++)
733 pfx += cppClassStack[i] + "::";
734 return pfx;
737 const nativeAnnotationRegex:RegExp = /^\[native\s*\((.*)\)\s*\]/;
739 // FIXME: Additional error checking we could add here:
740 // - only one data section per class, globally
742 function processFile(filename) {
743 cppDataStack.length = 0;
744 cppClassStack.length = 0;
746 const beforeReadLines = new Date();
747 const text = readLines(filename);
748 const afterReadLines = new Date();
750 const beforeProcessing = new Date();
751 const cppfile = Boolean(filename.match(/\.(h|cpp)$/));
752 const as3file = Boolean(filename.match(/\.as$/));
753 var lineno = 0;
755 for ( var i=0 ; i < text.length ; i++ ) {
756 var line = text[i];
757 var result;
759 lineno++;
761 // Quick precomputation to filter out lines that we don't
762 // need to examine any further with regular expressions. The
763 // regex search will start at the known good location.
765 var gcIndex = -1;
766 var nativeIndex = -1;
767 if (!as3file)
768 gcIndex = line.indexOf("GC_");
769 if (!cppfile)
770 nativeIndex = line.indexOf("[native");
771 if (gcIndex == -1 && nativeIndex == -1)
772 continue;
774 errorContext = "On " + filename + " line " + lineno;
776 // For line matching we match only at the start of the
777 // line after taking the substring starting at the
778 // known-good index. This yields the same performance as
779 // using a global regex and setting lastIndex to indicate
780 // where we want to start matching.
782 if (gcIndex >= 0) {
783 // C++ annotations.
785 if ((result = matchCppMetaTag(line, gcIndex)) != null) {
786 reportMatch(line);
787 var v = positionalAttrs(result[1], result[2], ")", null);
788 if (v is GCDataBegin || v is GCNoData) {
789 if (cppClassStack.length == 0 || cppClassStack[cppClassStack.length-1] != v.cls)
790 fail("Mismatched " + result[1] + " here: " + v.cls);
791 cppDataStack.push(v.cls);
793 if (v is GCDataEnd || v is GCNoData) {
794 var top = currentClassName();
795 if (v.cls != top)
796 fail(result[1] + " for " + v.cls + " but " + top + " is on the stack top");
797 cppDataStack.pop();
798 cppClassStack.pop();
800 if (!(v is GCDataSection)) {
801 if (v is GCClass) {
802 v.fullClassPrefix = stackToCppPrefix();
803 cppClassStack.push(v.cls);
805 specs.push(v);
808 else if ((result = matchCppFieldTag(line, gcIndex)) != null) {
809 reportMatch(line);
810 var v = positionalAttrs(result[1], result[2], ")", currentClassName());
811 specs.push(v);
815 if (nativeIndex >= 0) {
816 // AS3 annotations.
818 // For AS3 annotations we collect C++ class names if the [native] spec says
819 // that the C++ class should be exactly traced.
821 if ((result = nativeAnnotationRegex.exec(line.substring(nativeIndex))) != null) {
822 reportMatch(line);
823 var attr = parseNamedAttrs(result[1]);
824 var flags = {};
825 for ( var j=0 ; j < attr.length ; j++ ) {
826 if (attr[j] is Array)
827 flags[attr[j][0]] = attr[j][1];
829 if ("cls" in flags && ("classgc" in flags || "gc" in flags))
830 specs.push(new AS3Class(cppNamespace, flags["cls"]));
831 if ("instance" in flags && ("instancegc" in flags || "gc" in flags))
832 specs.push(new AS3Class(cppNamespace, flags["instance"]));
837 if (cppDataStack.length != 0)
838 fail("Missing GC_DATA_END for " + currentClassName());
840 if (cppClassStack.length != 0)
841 fail("Missing GC_DATA_BEGIN/GC_DATA_END for these: " + cppClassStack);
843 const afterProcessing = new Date();
845 readingTime += (afterReadLines - beforeReadLines);
846 processingTime += (afterProcessing - beforeProcessing);
849 for ( var i=0 ; i < files.length ; i++ )
850 processFile(files[i]);
852 if (profiling) {
853 print(" reading time = " + readingTime/1000 + "s");
854 print(" processing time = " + processingTime/1000 + "s");
855 print(" splitting time = " + splittingTime/1000 + "s");
859 function isVariableLength(t) {
860 return t is GCPointers || t is GCAtoms || t is GCStructures;
863 // Populate as3ClassList, as3ClassMap, cppClassList, and cppClassMap.
864 // Sort the lists.
866 function collectClasses()
868 errorContext = "Collecting classes";
870 for ( var i=0, limit=specs.length ; i < limit ; i++ ) {
871 var s = specs[i];
872 if (s is GCAS3Exact || s is GCCppExact) {
873 var clsname = s.cls;
874 if (cppClassMap.hasOwnProperty(clsname))
875 fail("Duplicate " + (s is GCAS3Exact ? "GC_AS3_EXACT" : "GC_CPP_EXACT") + " spec: " + clsname);
876 cppClassList.push(s);
877 cppClassMap[clsname] = s;
879 else if (s is AS3Class) {
880 var clsname = s.cls;
881 if (as3ClassMap.hasOwnProperty(clsname)) {
882 // Completely gross hack in the vector code - the instance for VectorClass and ObjectVectorClass
883 // are both ObjectVectorObject. Just work around it for now with this gross hack.
884 if (clsname == "ObjectVectorObject")
885 continue;
886 fail("Duplicate AS3 native spec: " + clsname);
888 as3ClassList.push(s);
889 as3ClassMap[clsname] = s;
893 as3ClassList.sort(function (a,b) { return strcmp(a.cls,b.cls) });
894 cppClassList.sort(function (a,b) { return strcmp(a.cls,b.cls) });
896 if (debug) {
897 print("C++ class map: " + cppClassMap);
898 print("AS3 class map: " + as3ClassMap);
902 // Check that the sets of classes correspond: every AS3 class must be
903 // a C++ GC_AS3_EXACT class, and vice versa. GC_CPP_EXACT classes must
904 // not have AS3 counterparts.
906 function checkClasses()
908 errorContext = "Checking class correspondence";
910 for ( var i=0 ; i < cppClassList.length ; i++ ) {
911 var c = cppClassList[i];
912 var n = c.cls;
913 var probe = as3ClassMap.hasOwnProperty(n);
914 if (c is GCCppExact) {
915 if (probe)
916 fail("AS3 side may not be defined for GC_CPP_EXACT " + n);
917 continue;
919 if (!probe)
920 fail("AS3 side is missing annotation for " + n);
923 for ( var i=0 ; i < as3ClassList.length ; i++ ) {
924 var c = as3ClassList[i];
925 var n = c.cls;
926 if (!cppClassMap.hasOwnProperty(n))
927 fail("C++ side is missing annotation for " + n);
928 if (!(cppClassMap[n] is GCAS3Exact))
929 fail("C++ side is not a GC_AS3_EXACT class for " + n);
933 // Add fields to classes, check for errors in class names, duplicates, etc.
934 // Sort the fields by name.
936 function collectFields()
938 errorContext = "Collecting fields";
940 for ( var i=0 ; i < specs.length ; i++ ) {
941 var s = specs[i];
942 if (s is GCClass || s is AS3Class)
943 continue;
944 if (!cppClassMap.hasOwnProperty(s.cls))
945 fail("Bad field annotation - unknown class: " + s.cls + " in " + s);
946 var c = cppClassMap[s.cls];
947 var fieldname = s.name;
948 if (s.if_) fieldname += "!if!" + s.if_;
949 if (s.ifdef) fieldname += "!ifdef!" + s.ifdef;
950 if (s.ifndef) fieldname += "!ifndef!" + s.ifndef;
951 if (isVariableLength(s)) {
952 // FIXME: It would be good to loosen the following restriction up; it
953 // would be sufficient to decree that only one of the arrays can be
954 // large. It is rarely the case that pointers or atom arrays are anything
955 // but trailing (and then there's only one), but eg MethodInfo has
956 // an inline fixed-size pointer array for the lookup cache, and if it
957 // were to have a trailing array as well then we'd run into this restriction.
958 if (c.variable_length_field != null)
959 fail("Arbitrary restriction: More than one variable length field on " + c);
960 if (c.fieldMap.hasOwnProperty(fieldname))
961 fail("Duplicate field name: " + s.name + "; canonically " + fieldname);
962 c.variable_length_field = s;
964 else {
965 if (c.fieldMap.hasOwnProperty(fieldname) || (c.variable_length_field != null && c.variable_length_field.name == fieldname))
966 fail("Duplicate field name: " + s.name + "; canonically " + fieldname);
967 c.fieldMap[fieldname] = s;
968 c.fieldList.push(s);
972 for ( var i=0 ; i < cppClassList.length ; i++ )
973 cppClassList[i].fieldList.sort(function (a,b) { return strcmp(a.name,b.name) });
976 // For each class compute whether it's likely to be large or small,
977 // this will influence how we generate code.
978 // A manifestly large number of fixed fields overrides any "small" hint.
979 // A pointer array will make us assume the object is large unless
980 // the hint is that it's small.
982 function computeLargeOrSmall()
984 for ( var i=0 ; i < cppClassList.length ; i++ ) {
985 var c = cppClassList[i];
986 if (c.fieldList.length >= largeObjectCutoff/4) // "4" is a proxy for word size, not correct on 64-bit systems but OK for this purpose
987 c.probablyLarge = true;
988 else if (c is GCCppExact && c.variable_length_field != null) {
989 c.hint = c.variable_length_field.hint;
990 c.probablyLarge = (c.hint != "small")
995 // If a class is large it could be because it has a pointer array or
996 // because it has many fixed fields or both. It is pretty much never
997 // going to be the case that it has many fixed fields so we always
998 // trace the fixed fields for cursor==0, technically that's suboptimal
999 // for incrementality but not a problem for correctness.
1001 function constructTracerBodies()
1003 function traceField(out, field) {
1004 if (field.ifdef)
1005 out.PR("#ifdef " + field.ifdef);
1007 if (field.ifndef)
1008 out.PR("#ifndef " + field.ifndef);
1010 if (field.if_)
1011 out.PR("#if " + field.if_);
1013 try {
1014 throw field;
1016 catch (f: GCPointer) { out.PR("gc->TraceLocation(&" + field.name + ");") }
1017 catch (f: GCConservative) { out.PR("gc->TraceConservativeLocation(&" + field.name + ");") }
1018 catch (f: GCStructure) { out.PR(field.name + ".gcTrace(gc);") }
1019 catch (f: GCAtom) { out.PR("gc->TraceAtom(&" + field.name + ");") }
1020 catch (f: *) {
1021 fail("Unknown type to trace: " + field);
1024 if (field.ifdef || field.ifndef || field.if_)
1025 out.PR("#endif");
1028 // Here we can do better: we can collect the fields that have the
1029 // same pointer attribute and the same ifdef attribute, and then
1030 // emit each group in a chunk. That will allow us to use the
1031 // multi-argument tracing functions when they're available.
1033 function emitFixedFields(c) {
1034 for ( var j=0 ; j < c.fieldList.length ; j++ ) {
1035 var f = c.fieldList[j];
1036 if (isVariableLength(f))
1037 fail("Invariant failure: there should be no variable length field among the fixed fields: " + f);
1038 traceField(c.out, f);
1042 function emitChunk(out, f, start, len) {
1043 try {
1044 throw f;
1046 catch (f:GCPointers) { c.out.PR("gc->TraceLocations((" + f.name + "+" + start + "), " + len + ");"); }
1047 catch (f:GCAtoms) { c.out.PR("gc->TraceAtoms((" + f.name + "+" + start + "), " + len + ");"); }
1048 catch (f:GCStructures) {
1049 c.out.
1050 PR("for ( size_t _xact_iter=0 ; _xact_iter < " + len + "; _xact_iter++ )").
1051 IN().
1052 PR(f.name + "[+_xact_iter+" + start + "].gcTrace(gc);").
1053 OUT();
1055 catch (f:*) {
1056 fail("Unknown variable length field type: " + f);
1060 // Important that the names introduced here do not shadow the ones
1061 // in the class so we prefix locals with _xact_.
1063 function emitArrayChunked(c) {
1064 c.out.
1065 PR("const size_t _xact_work_increment = " + largeObjectCutoff + "/sizeof(void*);").
1066 PR("const size_t _xact_work_count = " + c.variable_length_field.count + ";").
1067 PR("if (_xact_cursor * _xact_work_increment >= _xact_work_count)").
1068 IN().
1069 PR("return false;").
1070 OUT().
1071 PR("size_t _xact_work = _xact_work_increment;").
1072 PR("bool _xact_more = true;").
1073 PR("if ((_xact_cursor + 1) * _xact_work_increment >= _xact_work_count)").
1074 PR("{").
1075 IN().
1076 PR("_xact_work = _xact_work_count - (_xact_cursor * _xact_work_increment);").
1077 PR("_xact_more = false;").
1078 OUT().
1079 PR("}");
1080 emitChunk(c.out, c.variable_length_field, "(_xact_cursor * _xact_work_increment)", "_xact_work");
1081 c.out.PR("return _xact_more;");
1084 function emitArrayUnchunked(c) {
1085 emitChunk(c.out, c.variable_length_field, "0", c.variable_length_field.count);
1088 // FIXME: Not currently using this, but we could use it - probably elsewhere - to flag
1089 // probably-incorrect code.
1091 function noCredibleTracer(n) {
1092 switch (n) {
1093 case "MMgc::GCObject":
1094 case "GCObject":
1095 return true;
1096 default:
1097 return false;
1101 function noUsefulTracer(n) {
1102 switch (n) {
1103 case "MMgc::GCFinalizedObject":
1104 case "GCFinalizedObject":
1105 case "MMgc::GCTraceableObject":
1106 case "GCTraceableObject":
1107 case "MMgc::RCObject":
1108 case "RCObject":
1109 return true;
1110 default:
1111 return false;
1115 function cleanupNs(name) {
1116 // If the name has a namespace, keep it
1117 // Otherwise prepend the current output namespace
1118 if (name.match("::"))
1119 name = name.replace(/::/g, "_");
1120 else
1121 name = cppNamespace + "_" + name;
1122 // Brockets appear in template expansions: Fnord<A>
1123 // Spaces appear in nested template expressions: Fnord< A<B> >
1124 return name.replace(/ /g, "").replace(/<|>/g, "X");
1127 errorContext = "Accumulating bodies";
1129 for ( var i=0 ; i < cppClassList.length ; i++ ) {
1130 var c = cppClassList[i];
1132 // The interlock is just a #define with that name emitted at
1133 // the beginning of the output, we'll get a compilation error
1134 // if it's not defined. This way we ensure that every exactly
1135 // traced class has an exactly traced base class.
1137 if (c.base != null && !noUsefulTracer(c.base)) {
1138 c.out.PR(c.base + "::gcTrace(gc, 0);");
1139 if (interlockOutputFile != null)
1140 c.out.PR("(void)(" + cleanupNs(c.base) + "_isExactInterlock != 0);");
1143 if (c.hook)
1144 c.out.PR("gcTraceHook_" + c.cls + "(gc);");
1146 if (c.probablyLarge) {
1147 if (c.fieldList.length > 0) {
1148 c.out.
1149 PR("if (_xact_cursor == 0) {").
1150 IN();
1151 emitFixedFields(c);
1152 c.out.
1153 OUT().
1154 PR("}");
1156 if (c is GCClass && c.variable_length_field != null)
1157 emitArrayChunked(c);
1159 else {
1160 emitFixedFields(c);
1161 if (c is GCClass && c.variable_length_field != null)
1162 emitArrayUnchunked(c);
1167 function constructAndPrintTracers()
1169 var interlocks = interlockOutputFile ? new Printer() : null;
1170 var builtins = builtinOutputFile ? new Printer() : null;
1171 var natives = nativeOutputFile ? new Printer() : null;
1173 function emitInterlock(c) {
1174 if (interlocks)
1175 interlocks.PR("#define " + cppNamespace + "_" + c.cls + "_isExactInterlock 1");
1178 function emitTracers()
1180 for ( var i=0 ; i < cppClassList.length ; i++ ) {
1181 var c = cppClassList[i];
1182 var output = null;
1183 if (c is GCCppExact) {
1184 if (!natives)
1185 continue;
1186 output = natives;
1188 else {
1189 if (!builtins)
1190 continue;
1191 output = builtins;
1194 emitInterlock(c);
1195 if (c.ifdef)
1196 output.
1197 PR("#ifdef " + c.ifdef).
1198 NL();
1199 else if (c.ifndef)
1200 output.
1201 PR("#ifndef " + c.ifndef).
1202 NL();
1203 else if (c.if_)
1204 output.
1205 PR("#if " + c.if_).
1206 NL();
1207 if (c.probablyLarge) {
1208 output.
1209 PR("bool " + c.fullName() + "::gcTrace(MMgc::GC* gc, size_t _xact_cursor)").
1210 PR("{").
1211 DO(function (output) {
1212 if (output === builtins) {
1213 output.
1214 IN().
1215 PR("#ifndef GC_TRIVIAL_TRACER_" + c.cls).
1216 PR("if (_xact_cursor == 0)").
1217 PR("{").
1218 IN().
1219 PR("m_slots_" + c.cls + ".gcTracePrivateProperties(gc);").
1220 OUT().
1221 PR("}").
1222 PR("#endif").
1223 OUT();
1226 PR(c.out.get()).
1227 PR("}").
1228 NL();
1230 else {
1231 output.
1232 PR("bool " + c.fullName() + "::gcTrace(MMgc::GC* gc, size_t _xact_cursor)").
1233 PR("{").
1234 IN().
1235 PR("(void)gc;").
1236 PR("(void)_xact_cursor;").
1237 DO(function (output) {
1238 if (output === builtins) {
1239 output.
1240 PR("#ifndef GC_TRIVIAL_TRACER_" + c.cls).
1241 PR("m_slots_" + c.cls + ".gcTracePrivateProperties(gc);").
1242 PR("#endif");
1245 OUT().
1246 PR(c.out.get()).
1247 IN().
1248 PR("return false;").
1249 OUT().
1250 PR("}").
1251 NL();
1253 if (c.ifdef || c.ifndef || c.if_)
1254 output.
1255 PR("#endif // " + (c.ifdef || c.ifndef || c.if_)).
1256 NL();
1260 function emitDelegates()
1262 var output = "";
1263 for ( var i=0 ; i < cppClassList.length ; i++ ) {
1264 var c = cppClassList[i];
1265 if (c.base != null && c.base != "")
1266 output += ("#define GCDELEGATE_" + c.cls + " " + c.base + "\n");
1268 return output;
1271 function printToFile(fn, txt) {
1272 File.write(fn, txt);
1275 function nsOpen()
1277 if(cppNamespace == "")
1278 return "\n";
1279 else
1280 return "namespace " + cppNamespace +"\n{\n";
1283 function nsClose()
1285 if(cppNamespace == "")
1286 return "\n";
1287 else
1288 return "}\n";
1291 errorContext = "Emitting code";
1293 emitTracers();
1295 const doNotEdit = "\n/* machine generated file via utils/exactgc.as -- do not edit */\n\n";
1297 if (builtinOutputFile)
1298 printToFile(builtinOutputFile,
1299 (LICENSE +
1300 doNotEdit +
1301 nsOpen() +
1302 builtins.get() +
1303 (nativeOutputFile == builtinOutputFile ? natives.get() : "") +
1304 nsClose()));
1306 if (nativeOutputFile && nativeOutputFile != builtinOutputFile)
1307 printToFile(nativeOutputFile,
1308 (LICENSE +
1309 doNotEdit +
1310 nsOpen() +
1311 natives.get() +
1312 nsClose()));
1314 if (interlockOutputFile)
1315 printToFile(interlockOutputFile,
1316 (LICENSE +
1317 doNotEdit +
1318 interlocks.get() + "\n"));
1321 function profile(what, thunk)
1323 var then = new Date();
1324 var result = thunk();
1325 var now = new Date();
1326 if (profiling)
1327 print(what + ": " + (now - then)/1000 + "s");
1328 return result;
1331 function main()
1333 profile("readFiles", function() { readFiles(processOptionsAndFindFiles(System.argv)) });
1334 profile("collectClasses", collectClasses);
1335 profile("checkClasses", checkClasses);
1336 profile("collectFields", collectFields);
1337 profile("computeLargeOrSmall", computeLargeOrSmall);
1338 profile("constructTracerBodies",constructTracerBodies);
1339 profile("constructAndPrintTracers",constructAndPrintTracers);
1342 profile("main", main);