Skip to content

Commit 17e4647

Browse files
authored
Introduce more faster and memory optimal BitSet as replacment of Set<i32> (#2361)
1 parent 1b02776 commit 17e4647

File tree

4 files changed

+83
-8
lines changed

4 files changed

+83
-8
lines changed

Diff for: src/compiler.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ import {
188188
} from "./types";
189189

190190
import {
191+
BitSet,
191192
writeI8,
192193
writeI16,
193194
writeI32,
@@ -6748,7 +6749,7 @@ export class Compiler extends DiagnosticEmitter {
67486749
var previousFlow = this.currentFlow;
67496750
var flow = Flow.createInline(previousFlow.parentFunction, instance);
67506751
var body = [];
6751-
var usedLocals = new Set<i32>();
6752+
var usedLocals = new BitSet();
67526753

67536754
// Prepare compiled arguments right to left, keeping track of used locals.
67546755
for (let i = numArguments - 1; i >= 0; --i) {

Diff for: src/flow.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ import {
8484
} from "./ast";
8585

8686
import {
87+
BitSet,
8788
uniqueMap
8889
} from "./util";
8990

@@ -323,7 +324,7 @@ export class Flow {
323324
}
324325

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

460461
/** Adds a new scoped local of the specified name. */
461-
addScopedLocal(name: string, type: Type, except: Set<i32> | null = null): Local {
462+
addScopedLocal(name: string, type: Type, except: BitSet | null = null): Local {
462463
var scopedLocal = this.getTempLocal(type, except);
463464
scopedLocal.setTemporaryName(name);
464465
var scopedLocals = this.scopedLocals;

Diff for: src/passes/findusedlocals.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
* @license Apache-2.0
44
*/
55

6+
import {
7+
BitSet
8+
} from "../util";
9+
610
import {
711
Visitor
812
} from "./pass";
@@ -17,13 +21,13 @@ import {
1721
} from "../glue/binaryen";
1822

1923
class FindUsedLocalsVisitor extends Visitor {
20-
used: Set<i32>;
24+
used: BitSet;
2125

22-
constructor(used: Set<i32> = new Set()) {
26+
constructor(used: BitSet = new BitSet()) {
2327
super();
2428
this.used = used;
2529
}
26-
30+
2731
/** @override */
2832
visitLocalGet(localGet: ExpressionRef): void {
2933
this.used.add(<i32>_BinaryenLocalGetGetIndex(localGet));
@@ -40,8 +44,8 @@ var singleton: FindUsedLocalsVisitor | null = null;
4044
/** Finds the indexes of all locals used in the specified expression. */
4145
export function findUsedLocals(
4246
expr: ExpressionRef,
43-
used: Set<i32> = new Set()
44-
): Set<i32> {
47+
used: BitSet = new BitSet()
48+
): BitSet {
4549
var visitor = singleton;
4650
if (!visitor) singleton = visitor = new FindUsedLocalsVisitor(used);
4751
else visitor.used = used;

Diff for: src/util/collections.ts

+69
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,72 @@ export function uniqueMap<K,V>(original: Map<K,V> | null = null, overrides: Map<
2424
}
2525
return cloned;
2626
}
27+
28+
/** BitSet represent growable sequence of N bits. It's faster alternative of Set<i32> when elements
29+
* are not too much sparsed. Also it's more memory and cache efficient than Array<bool>.
30+
* The best way to use it for short bit sequences (less than 32*(2**16)).
31+
*/
32+
export class BitSet {
33+
words!: Uint32Array;
34+
35+
constructor() {
36+
this.clear();
37+
}
38+
39+
get size(): i32 {
40+
var count = 0;
41+
var words = this.words;
42+
for (let i = 0, len = words.length; i < len; i++) {
43+
let word = unchecked(words[i]);
44+
if (word) count += popcnt(word);
45+
}
46+
return count;
47+
}
48+
49+
add(index: i32): this {
50+
var idx = index >>> 5;
51+
var words = this.words;
52+
if (idx >= words.length) { // resize
53+
this.words = new Uint32Array(idx + 16);
54+
this.words.set(words);
55+
words = this.words;
56+
}
57+
unchecked(words[idx] |= 1 << index);
58+
return this;
59+
}
60+
61+
delete(index: i32): void {
62+
var idx = index >>> 5;
63+
var words = this.words;
64+
if (idx >= words.length) return;
65+
unchecked(words[idx] &= ~(1 << index));
66+
}
67+
68+
has(index: i32): bool {
69+
var idx = index >>> 5;
70+
var words = this.words;
71+
if (idx >= words.length) return false;
72+
return (unchecked(words[index >>> 5]) & (1 << index)) !== 0;
73+
}
74+
75+
clear(): void {
76+
this.words = new Uint32Array(16);
77+
}
78+
79+
toArray(): i32[] {
80+
var res = new Array<i32>(this.size);
81+
for (let i = 0, p = 0, len = this.words.length; i < len; ++i) {
82+
let word = unchecked(this.words[i]);
83+
while (word) {
84+
let mask = word & -word;
85+
unchecked(res[p++] = (i << 5) + popcnt(mask - 1));
86+
word ^= mask;
87+
}
88+
}
89+
return res;
90+
}
91+
92+
toString(): string {
93+
return `BitSet { ${this.toArray()} }`;
94+
}
95+
}

0 commit comments

Comments
 (0)