From 55512faa0d3fbd172835b2f343ce179941134ed6 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 20 Aug 2014 11:25:39 -0700 Subject: [PATCH] Use new tree to get Bloom filters --- src/services/{compiler => }/bloomFilter.ts | 81 +++++++++++-- src/services/compiler/references.ts | 1 - src/services/core/hash.ts | 112 ------------------ src/services/core/references.ts | 1 - src/services/services.ts | 80 ++++--------- .../cases/fourslash/referencesBloomFilters.ts | 20 ++++ .../fourslash/referencesBloomFilters2.ts | 20 ++++ .../fourslash/referencesBloomFilters3.ts | 16 +++ 8 files changed, 147 insertions(+), 184 deletions(-) rename src/services/{compiler => }/bloomFilter.ts (62%) delete mode 100644 src/services/core/hash.ts create mode 100644 tests/cases/fourslash/referencesBloomFilters.ts create mode 100644 tests/cases/fourslash/referencesBloomFilters2.ts create mode 100644 tests/cases/fourslash/referencesBloomFilters3.ts diff --git a/src/services/compiler/bloomFilter.ts b/src/services/bloomFilter.ts similarity index 62% rename from src/services/compiler/bloomFilter.ts rename to src/services/bloomFilter.ts index 44d72bf5bf..860b77f6bc 100644 --- a/src/services/compiler/bloomFilter.ts +++ b/src/services/bloomFilter.ts @@ -1,7 +1,5 @@ -/// - -module TypeScript { +module ts { export class BloomFilter { private bitArray: boolean[]; private hashFunctionCount: number; @@ -40,7 +38,7 @@ module TypeScript { } // m = ceil((n * log(p)) / log(1.0 / (pow(2.0, log(2.0))))) - static computeM(expectedCount: number): number { + private static computeM(expectedCount: number): number { var p: number = BloomFilter.falsePositiveProbability; var n: number = expectedCount; @@ -50,7 +48,7 @@ module TypeScript { } // k = round(log(2.0) * m / n) - static computeK(expectedCount: number): number { + private static computeK(expectedCount: number): number { var n: number = expectedCount; var m: number = BloomFilter.computeM(expectedCount); @@ -78,14 +76,12 @@ module TypeScript { * Murmur hash is public domain. Actual code is included below as reference. */ private computeHash(key: string, seed: number): number { - return Hash.computeMurmur2StringHashCode(key, seed); + return BloomFilter.computeMurmur2StringHashCode(key, seed); } - public addKeys(keys: ts.Map) { - for (var name in keys) { - if (ts.lookUp(keys, name)) { - this.add(name); - } + public addKeys(keys: string[]) { + for (var i = 0, n = keys.length; i < n; i++) { + this.add(keys[i]); } } @@ -114,7 +110,7 @@ module TypeScript { && this.hashFunctionCount === filter.hashFunctionCount; } - static isEquivalent(array1: boolean[], array2: boolean[]): boolean { + private static isEquivalent(array1: boolean[], array2: boolean[]): boolean { if (array1.length !== array2.length) { return false; } @@ -127,5 +123,64 @@ module TypeScript { return true; } + + private static integerMultiplyLow32Bits(n1: number, n2: number): number { + var n1Low16 = n1 & 0x0000ffff; + var n1High16 = n1 >>> 16; + + var n2Low16 = n2 & 0x0000ffff; + var n2High16 = n2 >>> 16; + + var resultLow32 = (((n1 & 0xffff0000) * n2) >>> 0) + (((n1 & 0x0000ffff) * n2) >>> 0) >>> 0; + return resultLow32; + } + + private static computeMurmur2StringHashCode(key: string, seed: number): number { + // 'm' and 'r' are mixing constants generated offline. + // They're not really 'magic', they just happen to work well. + + var m: number = 0x5bd1e995; + var r: number = 24; + + // Initialize the hash to a 'random' value + + var numberOfCharsLeft = key.length; + var h = Math.abs(seed ^ numberOfCharsLeft); + + // Mix 4 bytes at a time into the hash. NOTE: 4 bytes is two chars, so we iterate + // through the string two chars at a time. + var index = 0; + while (numberOfCharsLeft >= 2) { + var c1 = key.charCodeAt(index); + var c2 = key.charCodeAt(index + 1); + + var k = Math.abs(c1 | (c2 << 16)); + + k = BloomFilter.integerMultiplyLow32Bits(k, m); + k ^= k >> r; + k = BloomFilter.integerMultiplyLow32Bits(k, m); + + h = BloomFilter.integerMultiplyLow32Bits(h, m); + h ^= k; + + index += 2; + numberOfCharsLeft -= 2; + } + + // Handle the last char (or 2 bytes) if they exist. This happens if the original string had + // odd length. + if (numberOfCharsLeft === 1) { + h ^= key.charCodeAt(index); + h = BloomFilter.integerMultiplyLow32Bits(h, m); + } + + // Do a few final mixes of the hash to ensure the last few bytes are well-incorporated. + + h ^= h >> 13; + h = BloomFilter.integerMultiplyLow32Bits(h, m); + h ^= h >> 15; + + return h; + } } -} +} \ No newline at end of file diff --git a/src/services/compiler/references.ts b/src/services/compiler/references.ts index 464af87ec5..07ec0fa604 100644 --- a/src/services/compiler/references.ts +++ b/src/services/compiler/references.ts @@ -18,7 +18,6 @@ ///// ///// ///// -///// ///// ///// ///// diff --git a/src/services/core/hash.ts b/src/services/core/hash.ts deleted file mode 100644 index ef117df802..0000000000 --- a/src/services/core/hash.ts +++ /dev/null @@ -1,112 +0,0 @@ -/// - -module TypeScript { - export class Hash { - // This table uses FNV1a as a string hash - private static FNV_BASE = 2166136261; - private static FNV_PRIME = 16777619; - - private static computeFnv1aCharArrayHashCode(text: number[], start: number, len: number): number { - var hashCode = Hash.FNV_BASE; - var end = start + len; - - for (var i = start; i < end; i++) { - hashCode = IntegerUtilities.integerMultiplyLow32Bits(hashCode ^ text[i], Hash.FNV_PRIME); - } - - return hashCode; - } - - public static computeSimple31BitCharArrayHashCode(key: number[], start: number, len: number): number { - // Start with an int. - var hash = 0; - - for (var i = 0; i < len; i++) { - var ch = key[start + i]; - - // Left shift keeps things as a 32bit int. And we're only doing two adds. Chakra and - // V8 recognize this as not needing to go past the 53 bits needed for the float - // mantissa. Or'ing with 0 keeps this 32 bits. - hash = ((((hash << 5) - hash) | 0) + ch) | 0; - } - - // Ensure we fit in 31 bits. That way if/when this gets stored, it won't require any heap - // allocation. - return hash & 0x7FFFFFFF; - } - - public static computeSimple31BitStringHashCode(key: string): number { - // Start with an int. - var hash = 0; - - var start = 0; - var len = key.length; - - for (var i = 0; i < len; i++) { - var ch = key.charCodeAt(start + i); - - // Left shift keeps things as a 32bit int. And we're only doing two adds. Chakra and - // V8 recognize this as not needing to go past the 53 bits needed for the float - // mantissa. Or'ing with 0 keeps this 32 bits. - hash = ((((hash << 5) - hash) | 0) + ch) | 0; - } - - // Ensure we fit in 31 bits. That way if/when this gets stored, it won't require any heap - // allocation. - return hash & 0x7FFFFFFF; - } - - public static computeMurmur2StringHashCode(key: string, seed: number): number { - // 'm' and 'r' are mixing constants generated offline. - // They're not really 'magic', they just happen to work well. - - var m: number = 0x5bd1e995; - var r: number = 24; - - // Initialize the hash to a 'random' value - - var numberOfCharsLeft = key.length; - var h = Math.abs(seed ^ numberOfCharsLeft); - - // Mix 4 bytes at a time into the hash. NOTE: 4 bytes is two chars, so we iterate - // through the string two chars at a time. - var index = 0; - while (numberOfCharsLeft >= 2) { - var c1 = key.charCodeAt(index); - var c2 = key.charCodeAt(index + 1); - - var k = Math.abs(c1 | (c2 << 16)); - - k = IntegerUtilities.integerMultiplyLow32Bits(k, m); - k ^= k >> r; - k = IntegerUtilities.integerMultiplyLow32Bits(k, m); - - h = IntegerUtilities.integerMultiplyLow32Bits(h, m); - h ^= k; - - index += 2; - numberOfCharsLeft -= 2; - } - - // Handle the last char (or 2 bytes) if they exist. This happens if the original string had - // odd length. - if (numberOfCharsLeft === 1) { - h ^= key.charCodeAt(index); - h = IntegerUtilities.integerMultiplyLow32Bits(h, m); - } - - // Do a few final mixes of the hash to ensure the last few bytes are well-incorporated. - - h ^= h >> 13; - h = IntegerUtilities.integerMultiplyLow32Bits(h, m); - h ^= h >> 15; - - return h; - } - - public static combine(value: number, currentHash: number): number { - // Ensure we stay within 31 bits. - return (((currentHash << 5) + currentHash) + value) & 0x7FFFFFFF; - } - } -} \ No newline at end of file diff --git a/src/services/core/references.ts b/src/services/core/references.ts index fcfd10126e..8dcded411a 100644 --- a/src/services/core/references.ts +++ b/src/services/core/references.ts @@ -6,7 +6,6 @@ /// /// /// -/// /// /// /// diff --git a/src/services/services.ts b/src/services/services.ts index 9d52d93ad0..9a88490ac3 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -11,7 +11,7 @@ /// /// /// -/// +/// /// /// @@ -71,7 +71,7 @@ module ts { export interface SourceFile { getSourceUnit(): TypeScript.SourceUnitSyntax; getSyntaxTree(): TypeScript.SyntaxTree; - getBloomFilter(): TypeScript.BloomFilter; + getBloomFilter(): BloomFilter; update(scriptSnapshot: TypeScript.IScriptSnapshot, version: number, isOpen: boolean, textChangeRange: TypeScript.TextChangeRange): SourceFile; } @@ -324,7 +324,7 @@ module ts { public isOpen: boolean; public languageVersion: ScriptTarget; - private bloomFilter: TypeScript.BloomFilter; + private bloomFilter: BloomFilter; private syntaxTree: TypeScript.SyntaxTree; private scriptSnapshot: TypeScript.IScriptSnapshot; @@ -356,61 +356,27 @@ module ts { return TypeScript.isDTSFile(this.filename); } - private static isNameOfPropertyDeclaration(node: TypeScript.ISyntaxElement): boolean { - if (node.kind() !== TypeScript.SyntaxKind.IdentifierName && node.kind() !== TypeScript.SyntaxKind.StringLiteral && node.kind() !== TypeScript.SyntaxKind.NumericLiteral) { - return false; - } - - switch (node.parent.kind()) { - case TypeScript.SyntaxKind.VariableDeclarator: - return (node.parent).propertyName === node; - - case TypeScript.SyntaxKind.PropertySignature: - return (node.parent).propertyName === node; - - case TypeScript.SyntaxKind.SimplePropertyAssignment: - return (node.parent).propertyName === node; - - case TypeScript.SyntaxKind.EnumElement: - return (node.parent).propertyName === node; - - case TypeScript.SyntaxKind.ModuleDeclaration: - return (node.parent).name === node; - } - - return false; - } - - private static isElementAccessLiteralIndexer(node: TypeScript.ISyntaxElement): boolean { - return (node.kind() === TypeScript.SyntaxKind.StringLiteral || node.kind() === TypeScript.SyntaxKind.NumericLiteral) && - node.parent.kind() === TypeScript.SyntaxKind.ElementAccessExpression && ( node.parent).argumentExpression === node; - } - - public getBloomFilter(): TypeScript.BloomFilter { + public getBloomFilter(): BloomFilter { if (!this.bloomFilter) { - var identifiers = TypeScript.createIntrinsicsObject(); - var pre = function (cur: TypeScript.ISyntaxElement) { - if (TypeScript.ASTHelpers.isValidAstNode(cur)) { - if (cur.kind() === TypeScript.SyntaxKind.IdentifierName || - SourceFileObject.isNameOfPropertyDeclaration(cur) || - SourceFileObject.isElementAccessLiteralIndexer(cur)) { - var nodeText = TypeScript.tokenValueText((cur)); + var identifiers: string[] = []; - identifiers[nodeText] = true; - } - } - }; + forEachChild(this, function visit (node: Node) { + switch (node.kind) { + case SyntaxKind.Identifier: + identifiers.push((node).text); + return undefined; + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + if (isNameOfPropertyDeclaration(node) || isLiteralIndexOfIndexAccess(node)) { + identifiers.push((node).text); + } + return undefined; + default: + return forEachChild(node, visit); + }; + }); - TypeScript.getAstWalkerFactory().simpleWalk(this.getSourceUnit(), pre, null, identifiers); - - var identifierCount = 0; - for (var name in identifiers) { - if (identifiers[name]) { - identifierCount++; - } - } - - this.bloomFilter = new TypeScript.BloomFilter(identifierCount); + this.bloomFilter = new BloomFilter(identifiers.length); this.bloomFilter.addKeys(identifiers); } return this.bloomFilter; @@ -2375,7 +2341,7 @@ module ts { // type to the search set if (isNameOfPropertyAssignment(location)) { var symbolFromContextualType = getPropertySymbolFromContextualType(location); - if (symbolFromContextualType) result.push(symbolFromContextualType); + if (symbolFromContextualType) result.push(typeInfoResolver.getRootSymbol(symbolFromContextualType)); } // Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions @@ -2429,7 +2395,7 @@ module ts { // compare to our searchSymbol if (isNameOfPropertyAssignment(referenceLocation)) { var symbolFromContextualType = getPropertySymbolFromContextualType(referenceLocation); - if (searchSymbols.indexOf(symbolFromContextualType) >= 0) { + if (symbolFromContextualType && searchSymbols.indexOf(typeInfoResolver.getRootSymbol(symbolFromContextualType)) >= 0) { return true; } } diff --git a/tests/cases/fourslash/referencesBloomFilters.ts b/tests/cases/fourslash/referencesBloomFilters.ts new file mode 100644 index 0000000000..ea7dcfaaf0 --- /dev/null +++ b/tests/cases/fourslash/referencesBloomFilters.ts @@ -0,0 +1,20 @@ +/// + +// Ensure BloomFilter building logic is correct, by having one reference per file + +// @Filename: declaration.ts +////var container = { /*1*/searchProp : 1 }; + +// @Filename: expression.ts +////function blah() { return (1 + 2 + container./*2*/searchProp()) === 2; }; + +// @Filename: stringIndexer.ts +////function blah2() { container[/*3*/"searchProp"] }; + +// @Filename: redeclaration.ts +////container = { /*4*/"searchProp" : 18 }; + +test.markers().forEach(m => { + goTo.position(m.position, m.fileName); + verify.referencesCountIs(4); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/referencesBloomFilters2.ts b/tests/cases/fourslash/referencesBloomFilters2.ts new file mode 100644 index 0000000000..7599e2fe96 --- /dev/null +++ b/tests/cases/fourslash/referencesBloomFilters2.ts @@ -0,0 +1,20 @@ +/// + +// Ensure BloomFilter building logic is correct, by having one reference per file + +// @Filename: declaration.ts +////var container = { /*1*/42 : 1 }; + +// @Filename: expression.ts +////function blah() { return (container[/*2*/42]) === 2; }; + +// @Filename: stringIndexer.ts +////function blah2() { container[/*3*/"42"] }; + +// @Filename: redeclaration.ts +////container = { /*4*/"42" : 18 }; + +test.markers().forEach(m => { + goTo.position(m.position, m.fileName); + verify.referencesCountIs(4); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/referencesBloomFilters3.ts b/tests/cases/fourslash/referencesBloomFilters3.ts new file mode 100644 index 0000000000..7056bab239 --- /dev/null +++ b/tests/cases/fourslash/referencesBloomFilters3.ts @@ -0,0 +1,16 @@ +/// + +// Ensure BloomFilter building logic is correct, by having one reference per file + + +// @Filename: declaration.ts +////enum Test { /*1*/"42" = 1 }; + +// @Filename: expression.ts +////(Test[/*2*/42]); + + +test.markers().forEach(m => { + goTo.position(m.position, m.fileName); + verify.referencesCountIs(2); +}); \ No newline at end of file