blob: 84f0eea580d10fcef2ad01d15da638f782cc1bdc [file] [log] [blame]
Steve Blocka7e24c12009-10-30 11:49:00 +00001// Copyright 2009 the V8 project authors. All rights reserved.
2// Redistribution and use in source and binary forms, with or without
3// modification, are permitted provided that the following conditions are
4// met:
5//
6// * Redistributions of source code must retain the above copyright
7// notice, this list of conditions and the following disclaimer.
8// * Redistributions in binary form must reproduce the above
9// copyright notice, this list of conditions and the following
10// disclaimer in the documentation and/or other materials provided
11// with the distribution.
12// * Neither the name of Google Inc. nor the names of its
13// contributors may be used to endorse or promote products derived
14// from this software without specific prior written permission.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28
29function Profile(separateIc) {
30 devtools.profiler.Profile.call(this);
31 if (!separateIc) {
32 this.skipThisFunction = function(name) { return Profile.IC_RE.test(name); };
33 }
34};
35Profile.prototype = devtools.profiler.Profile.prototype;
36
37
38Profile.IC_RE =
39 /^(?:CallIC|LoadIC|StoreIC)|(?:Builtin: (?:Keyed)?(?:Call|Load|Store)IC_)/;
40
41
42/**
43 * A thin wrapper around shell's 'read' function showing a file name on error.
44 */
45function readFile(fileName) {
46 try {
47 return read(fileName);
48 } catch (e) {
49 print(fileName + ': ' + (e.message || e));
50 throw e;
51 }
52}
53
54
55function inherits(childCtor, parentCtor) {
56 function tempCtor() {};
57 tempCtor.prototype = parentCtor.prototype;
58 childCtor.prototype = new tempCtor();
59};
60
61
62function TickProcessor(
63 cppEntriesProvider, separateIc, ignoreUnknown, stateFilter) {
64 devtools.profiler.LogReader.call(this, {
65 'shared-library': { parsers: [null, parseInt, parseInt],
66 processor: this.processSharedLibrary },
67 'code-creation': {
68 parsers: [null, this.createAddressParser('code'), parseInt, null],
69 processor: this.processCodeCreation, backrefs: true },
70 'code-move': { parsers: [this.createAddressParser('code'),
71 this.createAddressParser('code-move-to')],
72 processor: this.processCodeMove, backrefs: true },
73 'code-delete': { parsers: [this.createAddressParser('code')],
74 processor: this.processCodeDelete, backrefs: true },
75 'tick': { parsers: [this.createAddressParser('code'),
76 this.createAddressParser('stack'), parseInt, 'var-args'],
77 processor: this.processTick, backrefs: true },
78 'profiler': null,
79 // Obsolete row types.
80 'code-allocate': null,
81 'begin-code-region': null,
82 'end-code-region': null });
83
84 this.cppEntriesProvider_ = cppEntriesProvider;
85 this.ignoreUnknown_ = ignoreUnknown;
86 this.stateFilter_ = stateFilter;
87 var ticks = this.ticks_ =
88 { total: 0, unaccounted: 0, excluded: 0, gc: 0 };
89
90 Profile.prototype.handleUnknownCode = function(
91 operation, addr, opt_stackPos) {
92 var op = devtools.profiler.Profile.Operation;
93 switch (operation) {
94 case op.MOVE:
95 print('Code move event for unknown code: 0x' + addr.toString(16));
96 break;
97 case op.DELETE:
98 print('Code delete event for unknown code: 0x' + addr.toString(16));
99 break;
100 case op.TICK:
101 // Only unknown PCs (the first frame) are reported as unaccounted,
102 // otherwise tick balance will be corrupted (this behavior is compatible
103 // with the original tickprocessor.py script.)
104 if (opt_stackPos == 0) {
105 ticks.unaccounted++;
106 }
107 break;
108 }
109 };
110
111 this.profile_ = new Profile(separateIc);
112 this.codeTypes_ = {};
113 // Count each tick as a time unit.
114 this.viewBuilder_ = new devtools.profiler.ViewBuilder(1);
115 this.lastLogFileName_ = null;
116};
117inherits(TickProcessor, devtools.profiler.LogReader);
118
119
120TickProcessor.VmStates = {
121 JS: 0,
122 GC: 1,
123 COMPILER: 2,
124 OTHER: 3,
125 EXTERNAL: 4
126};
127
128
129TickProcessor.CodeTypes = {
130 CPP: 0,
131 SHARED_LIB: 1
132};
133// Otherwise, this is JS-related code. We are not adding it to
134// codeTypes_ map because there can be zillions of them.
135
136
137TickProcessor.CALL_PROFILE_CUTOFF_PCT = 2.0;
138
139
140/**
141 * @override
142 */
143TickProcessor.prototype.printError = function(str) {
144 print(str);
145};
146
147
148TickProcessor.prototype.setCodeType = function(name, type) {
149 this.codeTypes_[name] = TickProcessor.CodeTypes[type];
150};
151
152
153TickProcessor.prototype.isSharedLibrary = function(name) {
154 return this.codeTypes_[name] == TickProcessor.CodeTypes.SHARED_LIB;
155};
156
157
158TickProcessor.prototype.isCppCode = function(name) {
159 return this.codeTypes_[name] == TickProcessor.CodeTypes.CPP;
160};
161
162
163TickProcessor.prototype.isJsCode = function(name) {
164 return !(name in this.codeTypes_);
165};
166
167
168TickProcessor.prototype.processLogFile = function(fileName) {
169 this.lastLogFileName_ = fileName;
170 var contents = readFile(fileName);
171 this.processLogChunk(contents);
172};
173
174
175TickProcessor.prototype.processSharedLibrary = function(
176 name, startAddr, endAddr) {
177 var entry = this.profile_.addLibrary(name, startAddr, endAddr);
178 this.setCodeType(entry.getName(), 'SHARED_LIB');
179
180 var self = this;
181 var libFuncs = this.cppEntriesProvider_.parseVmSymbols(
182 name, startAddr, endAddr, function(fName, fStart, fEnd) {
183 self.profile_.addStaticCode(fName, fStart, fEnd);
184 self.setCodeType(fName, 'CPP');
185 });
186};
187
188
189TickProcessor.prototype.processCodeCreation = function(
190 type, start, size, name) {
191 var entry = this.profile_.addCode(
192 this.expandAlias(type), name, start, size);
193};
194
195
196TickProcessor.prototype.processCodeMove = function(from, to) {
197 this.profile_.moveCode(from, to);
198};
199
200
201TickProcessor.prototype.processCodeDelete = function(start) {
202 this.profile_.deleteCode(start);
203};
204
205
206TickProcessor.prototype.includeTick = function(vmState) {
207 return this.stateFilter_ == null || this.stateFilter_ == vmState;
208};
209
210
211TickProcessor.prototype.processTick = function(pc, sp, vmState, stack) {
212 this.ticks_.total++;
213 if (vmState == TickProcessor.VmStates.GC) this.ticks_.gc++;
214 if (!this.includeTick(vmState)) {
215 this.ticks_.excluded++;
216 return;
217 }
218
219 this.profile_.recordTick(this.processStack(pc, stack));
220};
221
222
223TickProcessor.prototype.printStatistics = function() {
224 print('Statistical profiling result from ' + this.lastLogFileName_ +
225 ', (' + this.ticks_.total +
226 ' ticks, ' + this.ticks_.unaccounted + ' unaccounted, ' +
227 this.ticks_.excluded + ' excluded).');
228
229 if (this.ticks_.total == 0) return;
230
231 // Print the unknown ticks percentage if they are not ignored.
232 if (!this.ignoreUnknown_ && this.ticks_.unaccounted > 0) {
233 this.printHeader('Unknown');
234 this.printCounter(this.ticks_.unaccounted, this.ticks_.total);
235 }
236
237 var flatProfile = this.profile_.getFlatProfile();
238 var flatView = this.viewBuilder_.buildView(flatProfile);
239 // Sort by self time, desc, then by name, desc.
240 flatView.sort(function(rec1, rec2) {
241 return rec2.selfTime - rec1.selfTime ||
242 (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); });
243 var totalTicks = this.ticks_.total;
244 if (this.ignoreUnknown_) {
245 totalTicks -= this.ticks_.unaccounted;
246 }
247 // Our total time contains all the ticks encountered,
248 // while profile only knows about the filtered ticks.
249 flatView.head.totalTime = totalTicks;
250
251 // Count library ticks
252 var flatViewNodes = flatView.head.children;
253 var self = this;
254 var libraryTicks = 0;
255 this.processProfile(flatViewNodes,
256 function(name) { return self.isSharedLibrary(name); },
257 function(rec) { libraryTicks += rec.selfTime; });
258 var nonLibraryTicks = totalTicks - libraryTicks;
259
260 this.printHeader('Shared libraries');
261 this.printEntries(flatViewNodes, null,
262 function(name) { return self.isSharedLibrary(name); });
263
264 this.printHeader('JavaScript');
265 this.printEntries(flatViewNodes, nonLibraryTicks,
266 function(name) { return self.isJsCode(name); });
267
268 this.printHeader('C++');
269 this.printEntries(flatViewNodes, nonLibraryTicks,
270 function(name) { return self.isCppCode(name); });
271
272 this.printHeader('GC');
273 this.printCounter(this.ticks_.gc, totalTicks);
274
275 this.printHeavyProfHeader();
276 var heavyProfile = this.profile_.getBottomUpProfile();
277 var heavyView = this.viewBuilder_.buildView(heavyProfile);
278 // To show the same percentages as in the flat profile.
279 heavyView.head.totalTime = totalTicks;
280 // Sort by total time, desc, then by name, desc.
281 heavyView.sort(function(rec1, rec2) {
282 return rec2.totalTime - rec1.totalTime ||
283 (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); });
284 this.printHeavyProfile(heavyView.head.children);
285};
286
287
288function padLeft(s, len) {
289 s = s.toString();
290 if (s.length < len) {
291 var padLength = len - s.length;
292 if (!(padLength in padLeft)) {
293 padLeft[padLength] = new Array(padLength + 1).join(' ');
294 }
295 s = padLeft[padLength] + s;
296 }
297 return s;
298};
299
300
301TickProcessor.prototype.printHeader = function(headerTitle) {
302 print('\n [' + headerTitle + ']:');
303 print(' ticks total nonlib name');
304};
305
306
307TickProcessor.prototype.printHeavyProfHeader = function() {
308 print('\n [Bottom up (heavy) profile]:');
309 print(' Note: percentage shows a share of a particular caller in the ' +
310 'total\n' +
311 ' amount of its parent calls.');
312 print(' Callers occupying less than ' +
313 TickProcessor.CALL_PROFILE_CUTOFF_PCT.toFixed(1) +
314 '% are not shown.\n');
315 print(' ticks parent name');
316};
317
318
319TickProcessor.prototype.printCounter = function(ticksCount, totalTicksCount) {
320 var pct = ticksCount * 100.0 / totalTicksCount;
321 print(' ' + padLeft(ticksCount, 5) + ' ' + padLeft(pct.toFixed(1), 5) + '%');
322};
323
324
325TickProcessor.prototype.processProfile = function(
326 profile, filterP, func) {
327 for (var i = 0, n = profile.length; i < n; ++i) {
328 var rec = profile[i];
329 if (!filterP(rec.internalFuncName)) {
330 continue;
331 }
332 func(rec);
333 }
334};
335
336
337TickProcessor.prototype.printEntries = function(
338 profile, nonLibTicks, filterP) {
339 this.processProfile(profile, filterP, function (rec) {
340 if (rec.selfTime == 0) return;
341 var nonLibPct = nonLibTicks != null ?
342 rec.selfTime * 100.0 / nonLibTicks : 0.0;
343 print(' ' + padLeft(rec.selfTime, 5) + ' ' +
344 padLeft(rec.selfPercent.toFixed(1), 5) + '% ' +
345 padLeft(nonLibPct.toFixed(1), 5) + '% ' +
346 rec.internalFuncName);
347 });
348};
349
350
351TickProcessor.prototype.printHeavyProfile = function(profile, opt_indent) {
352 var self = this;
353 var indent = opt_indent || 0;
354 var indentStr = padLeft('', indent);
355 this.processProfile(profile, function() { return true; }, function (rec) {
356 // Cut off too infrequent callers.
357 if (rec.parentTotalPercent < TickProcessor.CALL_PROFILE_CUTOFF_PCT) return;
358 print(' ' + padLeft(rec.totalTime, 5) + ' ' +
359 padLeft(rec.parentTotalPercent.toFixed(1), 5) + '% ' +
360 indentStr + rec.internalFuncName);
361 // Limit backtrace depth.
362 if (indent < 10) {
363 self.printHeavyProfile(rec.children, indent + 2);
364 }
365 // Delimit top-level functions.
366 if (indent == 0) {
367 print('');
368 }
369 });
370};
371
372
373function CppEntriesProvider() {
374};
375
376
377CppEntriesProvider.prototype.parseVmSymbols = function(
378 libName, libStart, libEnd, processorFunc) {
379 this.loadSymbols(libName);
380
381 var prevEntry;
382
383 function addEntry(funcInfo) {
384 // Several functions can be mapped onto the same address. To avoid
385 // creating zero-sized entries, skip such duplicates.
386 // Also double-check that function belongs to the library address space.
387 if (prevEntry && !prevEntry.end &&
388 prevEntry.start < funcInfo.start &&
389 prevEntry.start >= libStart && funcInfo.start <= libEnd) {
390 processorFunc(prevEntry.name, prevEntry.start, funcInfo.start);
391 }
392 if (funcInfo.end &&
393 (!prevEntry || prevEntry.start != funcInfo.start) &&
394 funcInfo.start >= libStart && funcInfo.end <= libEnd) {
395 processorFunc(funcInfo.name, funcInfo.start, funcInfo.end);
396 }
397 prevEntry = funcInfo;
398 }
399
400 while (true) {
401 var funcInfo = this.parseNextLine();
402 if (funcInfo === null) {
403 continue;
404 } else if (funcInfo === false) {
405 break;
406 }
407 if (funcInfo.start < libStart && funcInfo.start < libEnd - libStart) {
408 funcInfo.start += libStart;
409 }
410 if (funcInfo.size) {
411 funcInfo.end = funcInfo.start + funcInfo.size;
412 }
413 addEntry(funcInfo);
414 }
415 addEntry({name: '', start: libEnd});
416};
417
418
419CppEntriesProvider.prototype.loadSymbols = function(libName) {
420};
421
422
423CppEntriesProvider.prototype.parseNextLine = function() {
424 return false;
425};
426
427
428function UnixCppEntriesProvider(nmExec) {
429 this.symbols = [];
430 this.parsePos = 0;
431 this.nmExec = nmExec;
432 this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ([0-9a-fA-F]{8,16} )?[tTwW] (.*)$/;
433};
434inherits(UnixCppEntriesProvider, CppEntriesProvider);
435
436
437UnixCppEntriesProvider.prototype.loadSymbols = function(libName) {
438 this.parsePos = 0;
439 try {
440 this.symbols = [
441 os.system(this.nmExec, ['-C', '-n', '-S', libName], -1, -1),
442 os.system(this.nmExec, ['-C', '-n', '-S', '-D', libName], -1, -1)
443 ];
444 } catch (e) {
445 // If the library cannot be found on this system let's not panic.
446 this.symbols = ['', ''];
447 }
448};
449
450
451UnixCppEntriesProvider.prototype.parseNextLine = function() {
452 if (this.symbols.length == 0) {
453 return false;
454 }
455 var lineEndPos = this.symbols[0].indexOf('\n', this.parsePos);
456 if (lineEndPos == -1) {
457 this.symbols.shift();
458 this.parsePos = 0;
459 return this.parseNextLine();
460 }
461
462 var line = this.symbols[0].substring(this.parsePos, lineEndPos);
463 this.parsePos = lineEndPos + 1;
464 var fields = line.match(this.FUNC_RE);
465 var funcInfo = null;
466 if (fields) {
467 funcInfo = { name: fields[3], start: parseInt(fields[1], 16) };
468 if (fields[2]) {
469 funcInfo.size = parseInt(fields[2], 16);
470 }
471 }
472 return funcInfo;
473};
474
475
476function MacCppEntriesProvider(nmExec) {
477 UnixCppEntriesProvider.call(this, nmExec);
478 // Note an empty group. It is required, as UnixCppEntriesProvider expects 3 groups.
479 this.FUNC_RE = /^([0-9a-fA-F]{8,16}) ()[iItT] (.*)$/;
480};
481inherits(MacCppEntriesProvider, UnixCppEntriesProvider);
482
483
484MacCppEntriesProvider.prototype.loadSymbols = function(libName) {
485 this.parsePos = 0;
486 try {
487 this.symbols = [os.system(this.nmExec, ['-n', '-f', libName], -1, -1), ''];
488 } catch (e) {
489 // If the library cannot be found on this system let's not panic.
490 this.symbols = '';
491 }
492};
493
494
495function WindowsCppEntriesProvider() {
496 this.symbols = '';
497 this.parsePos = 0;
498};
499inherits(WindowsCppEntriesProvider, CppEntriesProvider);
500
501
502WindowsCppEntriesProvider.FILENAME_RE = /^(.*)\.([^.]+)$/;
503
504
505WindowsCppEntriesProvider.FUNC_RE =
506 /^\s+0001:[0-9a-fA-F]{8}\s+([_\?@$0-9a-zA-Z]+)\s+([0-9a-fA-F]{8}).*$/;
507
508
509WindowsCppEntriesProvider.IMAGE_BASE_RE =
510 /^\s+0000:00000000\s+___ImageBase\s+([0-9a-fA-F]{8}).*$/;
511
512
513// This is almost a constant on Windows.
514WindowsCppEntriesProvider.EXE_IMAGE_BASE = 0x00400000;
515
516
517WindowsCppEntriesProvider.prototype.loadSymbols = function(libName) {
518 var fileNameFields = libName.match(WindowsCppEntriesProvider.FILENAME_RE);
519 if (!fileNameFields) return;
520 var mapFileName = fileNameFields[1] + '.map';
521 this.moduleType_ = fileNameFields[2].toLowerCase();
522 try {
523 this.symbols = read(mapFileName);
524 } catch (e) {
525 // If .map file cannot be found let's not panic.
526 this.symbols = '';
527 }
528};
529
530
531WindowsCppEntriesProvider.prototype.parseNextLine = function() {
532 var lineEndPos = this.symbols.indexOf('\r\n', this.parsePos);
533 if (lineEndPos == -1) {
534 return false;
535 }
536
537 var line = this.symbols.substring(this.parsePos, lineEndPos);
538 this.parsePos = lineEndPos + 2;
539
540 // Image base entry is above all other symbols, so we can just
541 // terminate parsing.
542 var imageBaseFields = line.match(WindowsCppEntriesProvider.IMAGE_BASE_RE);
543 if (imageBaseFields) {
544 var imageBase = parseInt(imageBaseFields[1], 16);
545 if ((this.moduleType_ == 'exe') !=
546 (imageBase == WindowsCppEntriesProvider.EXE_IMAGE_BASE)) {
547 return false;
548 }
549 }
550
551 var fields = line.match(WindowsCppEntriesProvider.FUNC_RE);
552 return fields ?
553 { name: this.unmangleName(fields[1]), start: parseInt(fields[2], 16) } :
554 null;
555};
556
557
558/**
559 * Performs very simple unmangling of C++ names.
560 *
561 * Does not handle arguments and template arguments. The mangled names have
562 * the form:
563 *
564 * ?LookupInDescriptor@JSObject@internal@v8@@...arguments info...
565 */
566WindowsCppEntriesProvider.prototype.unmangleName = function(name) {
567 // Empty or non-mangled name.
568 if (name.length < 1 || name.charAt(0) != '?') return name;
569 var nameEndPos = name.indexOf('@@');
570 var components = name.substring(1, nameEndPos).split('@');
571 components.reverse();
572 return components.join('::');
573};
574
575
576function ArgumentsProcessor(args) {
577 this.args_ = args;
578 this.result_ = ArgumentsProcessor.DEFAULTS;
579
580 this.argsDispatch_ = {
581 '-j': ['stateFilter', TickProcessor.VmStates.JS,
582 'Show only ticks from JS VM state'],
583 '-g': ['stateFilter', TickProcessor.VmStates.GC,
584 'Show only ticks from GC VM state'],
585 '-c': ['stateFilter', TickProcessor.VmStates.COMPILER,
586 'Show only ticks from COMPILER VM state'],
587 '-o': ['stateFilter', TickProcessor.VmStates.OTHER,
588 'Show only ticks from OTHER VM state'],
589 '-e': ['stateFilter', TickProcessor.VmStates.EXTERNAL,
590 'Show only ticks from EXTERNAL VM state'],
591 '--ignore-unknown': ['ignoreUnknown', true,
592 'Exclude ticks of unknown code entries from processing'],
593 '--separate-ic': ['separateIc', true,
594 'Separate IC entries'],
595 '--unix': ['platform', 'unix',
596 'Specify that we are running on *nix platform'],
597 '--windows': ['platform', 'windows',
598 'Specify that we are running on Windows platform'],
599 '--mac': ['platform', 'mac',
600 'Specify that we are running on Mac OS X platform'],
601 '--nm': ['nm', 'nm',
602 'Specify the \'nm\' executable to use (e.g. --nm=/my_dir/nm)']
603 };
604 this.argsDispatch_['--js'] = this.argsDispatch_['-j'];
605 this.argsDispatch_['--gc'] = this.argsDispatch_['-g'];
606 this.argsDispatch_['--compiler'] = this.argsDispatch_['-c'];
607 this.argsDispatch_['--other'] = this.argsDispatch_['-o'];
608 this.argsDispatch_['--external'] = this.argsDispatch_['-e'];
609};
610
611
612ArgumentsProcessor.DEFAULTS = {
613 logFileName: 'v8.log',
614 platform: 'unix',
615 stateFilter: null,
616 ignoreUnknown: false,
617 separateIc: false,
618 nm: 'nm'
619};
620
621
622ArgumentsProcessor.prototype.parse = function() {
623 while (this.args_.length) {
624 var arg = this.args_[0];
625 if (arg.charAt(0) != '-') {
626 break;
627 }
628 this.args_.shift();
629 var userValue = null;
630 var eqPos = arg.indexOf('=');
631 if (eqPos != -1) {
632 userValue = arg.substr(eqPos + 1);
633 arg = arg.substr(0, eqPos);
634 }
635 if (arg in this.argsDispatch_) {
636 var dispatch = this.argsDispatch_[arg];
637 this.result_[dispatch[0]] = userValue == null ? dispatch[1] : userValue;
638 } else {
639 return false;
640 }
641 }
642
643 if (this.args_.length >= 1) {
644 this.result_.logFileName = this.args_.shift();
645 }
646 return true;
647};
648
649
650ArgumentsProcessor.prototype.result = function() {
651 return this.result_;
652};
653
654
655ArgumentsProcessor.prototype.printUsageAndExit = function() {
656
657 function padRight(s, len) {
658 s = s.toString();
659 if (s.length < len) {
660 s = s + (new Array(len - s.length + 1).join(' '));
661 }
662 return s;
663 }
664
665 print('Cmdline args: [options] [log-file-name]\n' +
666 'Default log file name is "' +
667 ArgumentsProcessor.DEFAULTS.logFileName + '".\n');
668 print('Options:');
669 for (var arg in this.argsDispatch_) {
670 var synonims = [arg];
671 var dispatch = this.argsDispatch_[arg];
672 for (var synArg in this.argsDispatch_) {
673 if (arg !== synArg && dispatch === this.argsDispatch_[synArg]) {
674 synonims.push(synArg);
675 delete this.argsDispatch_[synArg];
676 }
677 }
678 print(' ' + padRight(synonims.join(', '), 20) + dispatch[2]);
679 }
680 quit(2);
681};
682