diff --git a/src/compiler.ts b/src/compiler.ts index 98d9a42084..3c39e7297d 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -188,6 +188,7 @@ import { } from "./types"; import { + BitSet, writeI8, writeI16, writeI32, @@ -6748,7 +6749,7 @@ export class Compiler extends DiagnosticEmitter { var previousFlow = this.currentFlow; var flow = Flow.createInline(previousFlow.parentFunction, instance); var body = []; - var usedLocals = new Set(); + var usedLocals = new BitSet(); // Prepare compiled arguments right to left, keeping track of used locals. for (let i = numArguments - 1; i >= 0; --i) { diff --git a/src/flow.ts b/src/flow.ts index 8e37112692..2ab9e4b4dc 100644 --- a/src/flow.ts +++ b/src/flow.ts @@ -84,6 +84,7 @@ import { } from "./ast"; import { + BitSet, uniqueMap } from "./util"; @@ -323,7 +324,7 @@ export class Flow { } /** Gets a free temporary local of the specified type. */ - getTempLocal(type: Type, except: Set | null = null): Local { + getTempLocal(type: Type, except: BitSet | null = null): Local { var parentFunction = this.parentFunction; var temps: Local[] | null; switch (type.toRef()) { @@ -458,7 +459,7 @@ export class Flow { } /** Adds a new scoped local of the specified name. */ - addScopedLocal(name: string, type: Type, except: Set | null = null): Local { + addScopedLocal(name: string, type: Type, except: BitSet | null = null): Local { var scopedLocal = this.getTempLocal(type, except); scopedLocal.setTemporaryName(name); var scopedLocals = this.scopedLocals; diff --git a/src/passes/findusedlocals.ts b/src/passes/findusedlocals.ts index acad07790f..bcfcada640 100644 --- a/src/passes/findusedlocals.ts +++ b/src/passes/findusedlocals.ts @@ -3,6 +3,10 @@ * @license Apache-2.0 */ +import { + BitSet +} from "../util"; + import { Visitor } from "./pass"; @@ -17,13 +21,13 @@ import { } from "../glue/binaryen"; class FindUsedLocalsVisitor extends Visitor { - used: Set; + used: BitSet; - constructor(used: Set = new Set()) { + constructor(used: BitSet = new BitSet()) { super(); this.used = used; } - + /** @override */ visitLocalGet(localGet: ExpressionRef): void { this.used.add(_BinaryenLocalGetGetIndex(localGet)); @@ -40,8 +44,8 @@ var singleton: FindUsedLocalsVisitor | null = null; /** Finds the indexes of all locals used in the specified expression. */ export function findUsedLocals( expr: ExpressionRef, - used: Set = new Set() -): Set { + used: BitSet = new BitSet() +): BitSet { var visitor = singleton; if (!visitor) singleton = visitor = new FindUsedLocalsVisitor(used); else visitor.used = used; diff --git a/src/util/collections.ts b/src/util/collections.ts index 8736666238..134ea073b0 100644 --- a/src/util/collections.ts +++ b/src/util/collections.ts @@ -24,3 +24,72 @@ export function uniqueMap(original: Map | null = null, overrides: Map< } return cloned; } + +/** BitSet represent growable sequence of N bits. It's faster alternative of Set when elements + * are not too much sparsed. Also it's more memory and cache efficient than Array. + * The best way to use it for short bit sequences (less than 32*(2**16)). + */ +export class BitSet { + words!: Uint32Array; + + constructor() { + this.clear(); + } + + get size(): i32 { + var count = 0; + var words = this.words; + for (let i = 0, len = words.length; i < len; i++) { + let word = unchecked(words[i]); + if (word) count += popcnt(word); + } + return count; + } + + add(index: i32): this { + var idx = index >>> 5; + var words = this.words; + if (idx >= words.length) { // resize + this.words = new Uint32Array(idx + 16); + this.words.set(words); + words = this.words; + } + unchecked(words[idx] |= 1 << index); + return this; + } + + delete(index: i32): void { + var idx = index >>> 5; + var words = this.words; + if (idx >= words.length) return; + unchecked(words[idx] &= ~(1 << index)); + } + + has(index: i32): bool { + var idx = index >>> 5; + var words = this.words; + if (idx >= words.length) return false; + return (unchecked(words[index >>> 5]) & (1 << index)) !== 0; + } + + clear(): void { + this.words = new Uint32Array(16); + } + + toArray(): i32[] { + var res = new Array(this.size); + for (let i = 0, p = 0, len = this.words.length; i < len; ++i) { + let word = unchecked(this.words[i]); + while (word) { + let mask = word & -word; + unchecked(res[p++] = (i << 5) + popcnt(mask - 1)); + word ^= mask; + } + } + return res; + } + + toString(): string { + return `BitSet { ${this.toArray()} }`; + } +}