Skip to content

Introduce more faster and memory optimal BitSet as replacment of Set<i32> #2361

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ import {
} from "./types";

import {
BitSet,
writeI8,
writeI16,
writeI32,
Expand Down Expand Up @@ -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<i32>();
var usedLocals = new BitSet();

// Prepare compiled arguments right to left, keeping track of used locals.
for (let i = numArguments - 1; i >= 0; --i) {
Expand Down
5 changes: 3 additions & 2 deletions src/flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ import {
} from "./ast";

import {
BitSet,
uniqueMap
} from "./util";

Expand Down Expand Up @@ -323,7 +324,7 @@ export class Flow {
}

/** Gets a free temporary local of the specified type. */
getTempLocal(type: Type, except: Set<i32> | null = null): Local {
getTempLocal(type: Type, except: BitSet | null = null): Local {
var parentFunction = this.parentFunction;
var temps: Local[] | null;
switch (<u32>type.toRef()) {
Expand Down Expand Up @@ -458,7 +459,7 @@ export class Flow {
}

/** Adds a new scoped local of the specified name. */
addScopedLocal(name: string, type: Type, except: Set<i32> | 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;
Expand Down
14 changes: 9 additions & 5 deletions src/passes/findusedlocals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
* @license Apache-2.0
*/

import {
BitSet
} from "../util";

import {
Visitor
} from "./pass";
Expand All @@ -17,13 +21,13 @@ import {
} from "../glue/binaryen";

class FindUsedLocalsVisitor extends Visitor {
used: Set<i32>;
used: BitSet;

constructor(used: Set<i32> = new Set()) {
constructor(used: BitSet = new BitSet()) {
super();
this.used = used;
}

/** @override */
visitLocalGet(localGet: ExpressionRef): void {
this.used.add(<i32>_BinaryenLocalGetGetIndex(localGet));
Expand All @@ -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<i32> = new Set()
): Set<i32> {
used: BitSet = new BitSet()
): BitSet {
var visitor = singleton;
if (!visitor) singleton = visitor = new FindUsedLocalsVisitor(used);
else visitor.used = used;
Expand Down
69 changes: 69 additions & 0 deletions src/util/collections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,72 @@ export function uniqueMap<K,V>(original: Map<K,V> | null = null, overrides: Map<
}
return cloned;
}

/** BitSet represent growable sequence of N bits. It's faster alternative of Set<i32> when elements
* are not too much sparsed. Also it's more memory and cache efficient than Array<bool>.
* 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<i32>(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()} }`;
}
}