Reduce allocations/gc by avoiding the creation of some CommentRange objects.

This commit is contained in:
Ron Buckton 2016-05-27 17:07:49 -07:00
parent fcd4ef443f
commit 43e3f357ee
6 changed files with 211 additions and 116 deletions

View file

@ -23,9 +23,9 @@ namespace ts {
let currentText: string;
let currentLineMap: number[];
let detachedCommentsInfo: { nodePos: number, detachedCommentEndPos: number}[];
// Tracks comment ranges that have already been consumed.
let consumedCommentRanges: Map<boolean>;
let hasWrittenComment = false;
let hasLastComment: boolean;
let lastCommentEnd: number;
return {
reset,
@ -122,7 +122,7 @@ namespace ts {
const skipTrailingComments = end < 0 || (emitFlags & NodeEmitFlags.NoTrailingComments) !== 0 || compilerOptions.removeComments;
if (!skipLeadingComments) {
emitDetachedCommentsAndUpdateCommentsInfo(detachedRange, compilerOptions.removeComments);
emitDetachedCommentsAndUpdateCommentsInfo(detachedRange);
}
if (extendedDiagnostics) {
@ -144,11 +144,12 @@ namespace ts {
}
function emitLeadingComments(pos: number, isEmittedNode: boolean) {
let leadingComments: CommentRange[];
hasWrittenComment = false;
if (isEmittedNode) {
leadingComments = getLeadingCommentsToEmit(pos);
forEachLeadingCommentToEmit(pos, emitLeadingComment);
}
else {
else if (pos === 0) {
// If the node will not be emitted in JS, remove all the comments(normal, pinned and ///) associated with the node,
// unless it is a triple slash comment at the top of the file.
// For Example:
@ -157,22 +158,52 @@ namespace ts {
// /// <reference-path ...>
// interface F {}
// The first /// will NOT be removed while the second one will be removed even though both node will not be emitted
if (pos === 0) {
leadingComments = filter(getLeadingCommentsToEmit(pos), isTripleSlashComment);
}
forEachLeadingCommentToEmit(pos, emitTripleSlashLeadingComment);
}
}
function emitTripleSlashLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) {
if (isTripleSlashComment(commentPos, commentEnd)) {
emitLeadingComment(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos);
}
}
function emitLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) {
if (!hasWrittenComment) {
emitNewLineBeforeLeadingCommentOfPosition(currentLineMap, writer, rangePos, commentPos);
hasWrittenComment = true;
}
emitNewLineBeforeLeadingCommentsOfPosition(currentLineMap, writer, pos, leadingComments);
// Leading comments are emitted at /*leading comment1 */space/*leading comment*/space
emitComments(currentText, currentLineMap, writer, leadingComments, /*leadingSeparator*/ false, /*trailingSeparator*/ true, newLine, writeComment);
emitPos(commentPos);
writeCommentRange(currentText, currentLineMap, writer, commentPos, commentEnd, newLine);
emitPos(commentEnd);
if (hasTrailingNewLine) {
writer.writeLine();
}
else {
writer.write(" ");
}
}
function emitTrailingComments(pos: number) {
const trailingComments = getTrailingCommentsToEmit(pos);
forEachTrailingCommentToEmit(pos, emitTrailingComment);
}
// trailing comments are emitted at space/*trailing comment1 */space/*trailing comment*/
emitComments(currentText, currentLineMap, writer, trailingComments, /*leadingSeparator*/ true, /*trailingSeparator*/ false, newLine, writeComment);
function emitTrailingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean) {
// trailing comments are emitted at space/*trailing comment1 */space/*trailing comment2*/
if (!writer.isAtStartOfLine()) {
writer.write(" ");
}
emitPos(commentPos);
writeCommentRange(currentText, currentLineMap, writer, commentPos, commentEnd, newLine);
emitPos(commentEnd);
if (hasTrailingNewLine) {
writer.writeLine();
}
}
function emitTrailingCommentsOfPosition(pos: number) {
@ -185,57 +216,52 @@ namespace ts {
commentStart = performance.mark();
}
const trailingComments = getTrailingCommentsToEmit(pos);
// trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space
emitComments(currentText, currentLineMap, writer, trailingComments, /*leadingSeparator*/ false, /*trailingSeparator*/ true, newLine, writeComment);
forEachTrailingCommentToEmit(pos, emitTrailingCommentOfPosition);
if (extendedDiagnostics) {
performance.measure("commentTime", commentStart);
}
}
function getLeadingCommentsToEmit(pos: number) {
function emitTrailingCommentOfPosition(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean) {
// trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space
emitPos(commentPos);
writeCommentRange(currentText, currentLineMap, writer, commentPos, commentEnd, newLine);
emitPos(commentEnd);
if (hasTrailingNewLine) {
writer.writeLine();
}
else {
writer.write(" ");
}
}
function forEachLeadingCommentToEmit(pos: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) {
// Emit the leading comments only if the container's pos doesn't match because the container should take care of emitting these comments
if (containerPos === -1 || pos !== containerPos) {
return hasDetachedComments(pos)
? getLeadingCommentsWithoutDetachedComments()
: getLeadingCommentRanges(currentText, pos);
if (hasDetachedComments(pos)) {
forEachLeadingCommentWithoutDetachedComments(cb);
}
else {
forEachLeadingCommentRange(currentText, pos, cb, /*state*/ pos);
}
}
}
function getTrailingCommentsToEmit(end: number) {
function forEachTrailingCommentToEmit(end: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean) => void) {
// Emit the trailing comments only if the container's end doesn't match because the container should take care of emitting these comments
if (containerEnd === -1 || (end !== containerEnd && end !== declarationListContainerEnd)) {
return getTrailingCommentRanges(currentText, end);
forEachTrailingCommentRange(currentText, end, cb);
}
}
/**
* Determine if the given comment is a triple-slash
*
* @return true if the comment is a triple-slash comment else false
**/
function isTripleSlashComment(comment: CommentRange) {
// Verify this is /// comment, but do the regexp match only when we first can find /// in the comment text
// so that we don't end up computing comment string and doing match for all // comments
if (currentText.charCodeAt(comment.pos + 1) === CharacterCodes.slash &&
comment.pos + 2 < comment.end &&
currentText.charCodeAt(comment.pos + 2) === CharacterCodes.slash) {
const textSubStr = currentText.substring(comment.pos, comment.end);
return textSubStr.match(fullTripleSlashReferencePathRegEx) ||
textSubStr.match(fullTripleSlashAMDReferencePathRegEx) ?
true : false;
}
return false;
}
function reset() {
currentSourceFile = undefined;
currentText = undefined;
currentLineMap = undefined;
detachedCommentsInfo = undefined;
consumedCommentRanges = undefined;
}
function setSourceFile(sourceFile: SourceFile) {
@ -243,17 +269,15 @@ namespace ts {
currentText = currentSourceFile.text;
currentLineMap = getLineStarts(currentSourceFile);
detachedCommentsInfo = undefined;
consumedCommentRanges = {};
}
function hasDetachedComments(pos: number) {
return detachedCommentsInfo !== undefined && lastOrUndefined(detachedCommentsInfo).nodePos === pos;
}
function getLeadingCommentsWithoutDetachedComments() {
function forEachLeadingCommentWithoutDetachedComments(cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) {
// get the leading comments from detachedPos
const pos = lastOrUndefined(detachedCommentsInfo).detachedCommentEndPos;
const leadingComments = getLeadingCommentRanges(currentText, pos);
if (detachedCommentsInfo.length - 1) {
detachedCommentsInfo.pop();
}
@ -261,12 +285,11 @@ namespace ts {
detachedCommentsInfo = undefined;
}
return leadingComments;
forEachLeadingCommentRange(currentText, pos, cb, /*state*/ pos);
}
function emitDetachedCommentsAndUpdateCommentsInfo(node: TextRange, removeComments: boolean) {
const currentDetachedCommentInfo = emitDetachedComments(currentText, currentLineMap, writer, writeComment, node, newLine, removeComments);
function emitDetachedCommentsAndUpdateCommentsInfo(range: TextRange) {
const currentDetachedCommentInfo = emitDetachedComments(currentText, currentLineMap, writer, writeComment, range, newLine, compilerOptions.removeComments);
if (currentDetachedCommentInfo) {
if (detachedCommentsInfo) {
detachedCommentsInfo.push(currentDetachedCommentInfo);
@ -277,10 +300,29 @@ namespace ts {
}
}
function writeComment(text: string, lineMap: number[], writer: EmitTextWriter, comment: CommentRange, newLine: string) {
emitPos(comment.pos);
writeCommentRange(text, lineMap, writer, comment, newLine);
emitPos(comment.end);
function writeComment(text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) {
emitPos(commentPos);
writeCommentRange(text, lineMap, writer, commentPos, commentEnd, newLine);
emitPos(commentEnd);
}
/**
* Determine if the given comment is a triple-slash
*
* @return true if the comment is a triple-slash comment else false
**/
function isTripleSlashComment(commentPos: number, commentEnd: number) {
// Verify this is /// comment, but do the regexp match only when we first can find /// in the comment text
// so that we don't end up computing comment string and doing match for all // comments
if (currentText.charCodeAt(commentPos + 1) === CharacterCodes.slash &&
commentPos + 2 < commentEnd &&
currentText.charCodeAt(commentPos + 2) === CharacterCodes.slash) {
const textSubStr = currentText.substring(commentPos, commentEnd);
return textSubStr.match(fullTripleSlashReferencePathRegEx) ||
textSubStr.match(fullTripleSlashAMDReferencePathRegEx) ?
true : false;
}
return false;
}
}
}

View file

@ -1139,8 +1139,8 @@ namespace ts {
/** Performance measurements for the compiler. */
/*@internal*/
export namespace performance {
let counters: Map<number> = {};
let measures: Map<number> = {};
let counters: Map<number>;
let measures: Map<number>;
let enabled = false;
/**
@ -1191,22 +1191,32 @@ namespace ts {
return enabled && getProperty(measures, measureName) || 0;
}
/**
* Resets all marks and measurements in the performance service.
*/
export function reset() {
counters = {};
measures = {};
}
/** Enables performance measurements for the compiler. */
export function enable() {
enabled = true;
if (!enabled) {
enabled = true;
counters = { };
measures = {
programTime: 0,
parseTime: 0,
bindTime: 0,
emitTime: 0,
ioReadTime: 0,
ioWriteTime: 0,
printTime: 0,
commentTime: 0,
sourceMapTime: 0
};
}
}
/** Disables performance measurements for the compiler. */
export function disable() {
enabled = false;
if (enabled) {
enabled = false;
counters = undefined;
measures = undefined;
}
}
}
}

View file

@ -8228,10 +8228,10 @@ const _super = (function (geti, seti) {
}
}
function writeComment(text: string, lineMap: number[], writer: EmitTextWriter, comment: CommentRange, newLine: string) {
emitPos(comment.pos);
writeCommentRange(text, lineMap, writer, comment, newLine);
emitPos(comment.end);
function writeComment(text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) {
emitPos(commentPos);
writeCommentRange(text, lineMap, writer, commentPos, commentEnd, newLine);
emitPos(commentEnd);
}
function emitShebang() {

View file

@ -591,10 +591,15 @@ namespace ts {
* and the next token are returned.
* If true, comments occurring between the given position and the next line break are returned.
*/
function getCommentRanges(text: string, pos: number, trailing: boolean): CommentRange[] {
let result: CommentRange[];
function iterateCommentRanges<T, U>(reduce: boolean, text: string, pos: number, trailing: boolean, cb: (pos: number, end: number, kind: SyntaxKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial?: U): U {
let pendingPos: number;
let pendingEnd: number;
let pendingKind: SyntaxKind;
let pendingHasTrailingNewLine: boolean;
let hasPendingCommentRange = false;
let collecting = trailing || pos === 0;
while (pos >= 0 && pos < text.length) {
let accumulator = initial;
scan: while (pos >= 0 && pos < text.length) {
const ch = text.charCodeAt(pos);
switch (ch) {
case CharacterCodes.carriageReturn:
@ -604,12 +609,14 @@ namespace ts {
case CharacterCodes.lineFeed:
pos++;
if (trailing) {
return result;
break scan;
}
collecting = true;
if (result && result.length) {
lastOrUndefined(result).hasTrailingNewLine = true;
if (hasPendingCommentRange) {
pendingHasTrailingNewLine = true;
}
continue;
case CharacterCodes.tab:
case CharacterCodes.verticalTab:
@ -644,38 +651,76 @@ namespace ts {
}
if (collecting) {
if (!result) {
result = [];
if (hasPendingCommentRange) {
accumulator = cb(pendingPos, pendingEnd, pendingKind, pendingHasTrailingNewLine, state, accumulator);
if (!reduce && accumulator) {
// If we are not reducing and we have a truthy result, return it.
return accumulator;
}
hasPendingCommentRange = false;
}
result.push({ pos: startPos, end: pos, hasTrailingNewLine, kind });
pendingPos = startPos;
pendingEnd = pos;
pendingKind = kind;
pendingHasTrailingNewLine = hasTrailingNewLine;
hasPendingCommentRange = true;
}
continue;
}
break;
break scan;
default:
if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpace(ch) || isLineBreak(ch))) {
if (result && result.length && isLineBreak(ch)) {
lastOrUndefined(result).hasTrailingNewLine = true;
if (hasPendingCommentRange && isLineBreak(ch)) {
pendingHasTrailingNewLine = true;
}
pos++;
continue;
}
break;
break scan;
}
return result;
}
return result;
if (hasPendingCommentRange) {
accumulator = cb(pendingPos, pendingEnd, pendingKind, pendingHasTrailingNewLine, state, accumulator);
}
return accumulator;
}
export function forEachLeadingCommentRange<T, U>(text: string, pos: number, cb: (pos: number, end: number, kind: SyntaxKind, hasTrailingNewLine: boolean, state: T) => U, state?: T) {
return iterateCommentRanges(/*reduce*/ false, text, pos, /*trailing*/ false, cb, state);
}
export function forEachTrailingCommentRange<T, U>(text: string, pos: number, cb: (pos: number, end: number, kind: SyntaxKind, hasTrailingNewLine: boolean, state: T) => U, state?: T) {
return iterateCommentRanges(/*reduce*/ false, text, pos, /*trailing*/ true, cb, state);
}
export function reduceEachLeadingCommentRange<T, U>(text: string, pos: number, cb: (pos: number, end: number, kind: SyntaxKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U) {
return iterateCommentRanges(/*reduce*/ true, text, pos, /*trailing*/ false, cb, state, initial);
}
export function reduceEachTrailingCommentRange<T, U>(text: string, pos: number, cb: (pos: number, end: number, kind: SyntaxKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U) {
return iterateCommentRanges(/*reduce*/ true, text, pos, /*trailing*/ true, cb, state, initial);
}
function appendCommentRange(pos: number, end: number, kind: SyntaxKind, hasTrailingNewLine: boolean, state: any, comments: CommentRange[]) {
if (!comments) {
comments = [];
}
comments.push({ pos, end, hasTrailingNewLine, kind });
return comments;
}
export function getLeadingCommentRanges(text: string, pos: number): CommentRange[] {
return getCommentRanges(text, pos, /*trailing*/ false);
return reduceEachLeadingCommentRange(text, pos, appendCommentRange, undefined, undefined);
}
export function getTrailingCommentRanges(text: string, pos: number): CommentRange[] {
return getCommentRanges(text, pos, /*trailing*/ true);
return reduceEachTrailingCommentRange(text, pos, appendCommentRange, undefined, undefined);
}
/** Optionally, get the shebang */

View file

@ -11,16 +11,6 @@ namespace ts {
value: string;
}
interface Mark {
markName: string;
count: number;
}
interface Measure {
measureName: string;
duration: number;
}
let reportDiagnostic = reportDiagnosticSimply;
function reportDiagnostics(diagnostics: Diagnostic[], host: CompilerHost): void {
@ -560,7 +550,6 @@ namespace ts {
let statistics: Statistic[];
if (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics) {
performance.enable();
performance.reset();
statistics = [];
}
@ -610,7 +599,6 @@ namespace ts {
reportStatistics();
performance.disable();
performance.reset();
}
return { program, exitStatus };

View file

@ -2285,6 +2285,7 @@ namespace ts {
getLine(): number;
getColumn(): number;
getIndent(): number;
isAtStartOfLine(): boolean;
reset(): void;
}
@ -2373,6 +2374,7 @@ namespace ts {
getLine: () => lineCount + 1,
getColumn: () => lineStart ? indent * getIndentSize() + 1 : output.length - linePos + 1,
getText: () => output,
isAtStartOfLine: () => lineStart,
reset
};
}
@ -2601,8 +2603,16 @@ namespace ts {
}
}
export function emitNewLineBeforeLeadingCommentOfPosition(lineMap: number[], writer: EmitTextWriter, pos: number, commentPos: number) {
// If the leading comments start on different line than the start of node, write new line
if (pos !== commentPos &&
getLineOfLocalPositionFromLineMap(lineMap, pos) !== getLineOfLocalPositionFromLineMap(lineMap, commentPos)) {
writer.writeLine();
}
}
export function emitComments(text: string, lineMap: number[], writer: EmitTextWriter, comments: CommentRange[], leadingSeparator: boolean, trailingSeparator: boolean, newLine: string,
writeComment: (text: string, lineMap: number[], writer: EmitTextWriter, comment: CommentRange, newLine: string) => void) {
writeComment: (text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) => void) {
if (comments && comments.length > 0) {
if (leadingSeparator) {
writer.write(" ");
@ -2615,7 +2625,7 @@ namespace ts {
emitInterveningSeparator = false;
}
writeComment(text, lineMap, writer, comment, newLine);
writeComment(text, lineMap, writer, comment.pos, comment.end, newLine);
if (comment.hasTrailingNewLine) {
writer.writeLine();
}
@ -2635,7 +2645,7 @@ namespace ts {
* the next statement by space.
*/
export function emitDetachedComments(text: string, lineMap: number[], writer: EmitTextWriter,
writeComment: (text: string, lineMap: number[], writer: EmitTextWriter, comment: CommentRange, newLine: string) => void,
writeComment: (text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) => void,
node: TextRange, newLine: string, removeComments: boolean) {
let leadingComments: CommentRange[];
let currentDetachedCommentInfo: {nodePos: number, detachedCommentEndPos: number};
@ -2699,20 +2709,20 @@ namespace ts {
}
export function writeCommentRange(text: string, lineMap: number[], writer: EmitTextWriter, comment: CommentRange, newLine: string) {
if (text.charCodeAt(comment.pos + 1) === CharacterCodes.asterisk) {
const firstCommentLineAndCharacter = computeLineAndCharacterOfPosition(lineMap, comment.pos);
export function writeCommentRange(text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) {
if (text.charCodeAt(commentPos + 1) === CharacterCodes.asterisk) {
const firstCommentLineAndCharacter = computeLineAndCharacterOfPosition(lineMap, commentPos);
const lineCount = lineMap.length;
let firstCommentLineIndent: number;
for (let pos = comment.pos, currentLine = firstCommentLineAndCharacter.line; pos < comment.end; currentLine++) {
for (let pos = commentPos, currentLine = firstCommentLineAndCharacter.line; pos < commentEnd; currentLine++) {
const nextLineStart = (currentLine + 1) === lineCount
? text.length + 1
: lineMap[currentLine + 1];
if (pos !== comment.pos) {
if (pos !== commentPos) {
// If we are not emitting first line, we need to write the spaces to adjust the alignment
if (firstCommentLineIndent === undefined) {
firstCommentLineIndent = calculateIndent(text, lineMap[firstCommentLineAndCharacter.line], comment.pos);
firstCommentLineIndent = calculateIndent(text, lineMap[firstCommentLineAndCharacter.line], commentPos);
}
// These are number of spaces writer is going to write at current indent
@ -2753,24 +2763,24 @@ namespace ts {
}
// Write the comment line text
writeTrimmedCurrentLine(text, comment, writer, newLine, pos, nextLineStart);
writeTrimmedCurrentLine(text, commentEnd, writer, newLine, pos, nextLineStart);
pos = nextLineStart;
}
}
else {
// Single line comment of style //....
writer.write(text.substring(comment.pos, comment.end));
writer.write(text.substring(commentPos, commentEnd));
}
}
function writeTrimmedCurrentLine(text: string, comment: CommentRange, writer: EmitTextWriter, newLine: string, pos: number, nextLineStart: number) {
const end = Math.min(comment.end, nextLineStart - 1);
function writeTrimmedCurrentLine(text: string, commentEnd: number, writer: EmitTextWriter, newLine: string, pos: number, nextLineStart: number) {
const end = Math.min(commentEnd, nextLineStart - 1);
const currentLineText = text.substring(pos, end).replace(/^\s+|\s+$/g, "");
if (currentLineText) {
// trimmed forward and ending spaces text
writer.write(currentLineText);
if (end !== comment.end) {
if (end !== commentEnd) {
writer.writeLine();
}
}