From 57602ceaf5146f72e3923498a3e691ba7f5d3d4c Mon Sep 17 00:00:00 2001 From: MaxGraey Date: Mon, 4 Jul 2022 13:56:55 +0300 Subject: [PATCH 1/7] introduce more faster and memory optimal BitSet --- src/compiler.ts | 3 +- src/flow.ts | 5 +-- src/passes/findusedlocals.ts | 14 +++++--- src/util/collections.ts | 64 ++++++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 8 deletions(-) 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..6418851ef6 100644 --- a/src/util/collections.ts +++ b/src/util/collections.ts @@ -24,3 +24,67 @@ export function uniqueMap(original: Map | null = null, overrides: Map< } return cloned; } + +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++) { + count += popcnt(unchecked(words[i])); + } + return count; + } + + add(index: i32): this { + var idx = index >>> 5; + if (idx > this.words.length) { + this.resize(index); + } + unchecked(this.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 { + return (this.words[index >>> 5] & (1 << index)) !== 0; + } + + clear(): void { + this.words = new Uint32Array(8); + } + + private resize(index: i32): void { + var newWords = new Uint32Array((index + 64) >>> 5); + newWords.set(this.words); + this.words = newWords; + } + + 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; + res[p++] = (i << 5) + popcnt(mask - 1); + word ^= mask; + } + } + return res; + } + + toString(): string { + return `BitSet { ${this.toArray()} }`; + } +} From fa947a2907e7b6ef3d588e2ed650af759261e2d9 Mon Sep 17 00:00:00 2001 From: MaxGraey Date: Mon, 4 Jul 2022 14:01:14 +0300 Subject: [PATCH 2/7] safy BitSet#has --- src/util/collections.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/util/collections.ts b/src/util/collections.ts index 6418851ef6..72603443be 100644 --- a/src/util/collections.ts +++ b/src/util/collections.ts @@ -58,7 +58,10 @@ export class BitSet { } has(index: i32): bool { - return (this.words[index >>> 5] & (1 << index)) !== 0; + var idx = index >>> 5; + var words = this.words; + if (idx >= words.length) return false; + return (unchecked(words[index >>> 5]) & (1 << index)) !== 0; } clear(): void { From 25a8e0c3fc3716405fabcee07a327413e5ce641c Mon Sep 17 00:00:00 2001 From: MaxGraey Date: Mon, 4 Jul 2022 14:57:01 +0300 Subject: [PATCH 3/7] simplify --- src/util/collections.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/util/collections.ts b/src/util/collections.ts index 72603443be..f3ee22f17f 100644 --- a/src/util/collections.ts +++ b/src/util/collections.ts @@ -43,10 +43,13 @@ export class BitSet { add(index: i32): this { var idx = index >>> 5; - if (idx > this.words.length) { - this.resize(index); + var words = this.words; + if (idx > words.length) { // resize + this.words = new Uint32Array(idx + 8); + this.words.set(words); + words = this.words; } - unchecked(this.words[idx] |= 1 << index); + unchecked(words[idx] |= 1 << index); return this; } @@ -68,12 +71,6 @@ export class BitSet { this.words = new Uint32Array(8); } - private resize(index: i32): void { - var newWords = new Uint32Array((index + 64) >>> 5); - newWords.set(this.words); - this.words = newWords; - } - toArray(): i32[] { var res = new Array(this.size); for (let i = 0, p = 0, len = this.words.length; i < len; ++i) { From 380d212136d8e6edb40723a76df0e6f1068cc172 Mon Sep 17 00:00:00 2001 From: MaxGraey Date: Mon, 4 Jul 2022 14:59:01 +0300 Subject: [PATCH 4/7] tune --- src/util/collections.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/util/collections.ts b/src/util/collections.ts index f3ee22f17f..3c8ee5006f 100644 --- a/src/util/collections.ts +++ b/src/util/collections.ts @@ -45,7 +45,7 @@ export class BitSet { var idx = index >>> 5; var words = this.words; if (idx > words.length) { // resize - this.words = new Uint32Array(idx + 8); + this.words = new Uint32Array(idx + 16); this.words.set(words); words = this.words; } @@ -68,7 +68,7 @@ export class BitSet { } clear(): void { - this.words = new Uint32Array(8); + this.words = new Uint32Array(16); } toArray(): i32[] { @@ -77,7 +77,7 @@ export class BitSet { let word = unchecked(this.words[i]); while (word) { let mask = word & -word; - res[p++] = (i << 5) + popcnt(mask - 1); + unchecked(res[p++] = (i << 5) + popcnt(mask - 1)); word ^= mask; } } From d786577a55e3a3eba0c034a02c961be2dc1007ed Mon Sep 17 00:00:00 2001 From: MaxGraey Date: Mon, 4 Jul 2022 15:00:25 +0300 Subject: [PATCH 5/7] better size --- src/util/collections.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/util/collections.ts b/src/util/collections.ts index 3c8ee5006f..a5ea7cc7cb 100644 --- a/src/util/collections.ts +++ b/src/util/collections.ts @@ -36,7 +36,8 @@ export class BitSet { var count = 0; var words = this.words; for (let i = 0, len = words.length; i < len; i++) { - count += popcnt(unchecked(words[i])); + let word = unchecked(words[i]); + if (word) count += popcnt(word); } return count; } From 5fdf4bcbf846a1b04c4df11cdb54996fdf008d39 Mon Sep 17 00:00:00 2001 From: MaxGraey Date: Wed, 6 Jul 2022 16:52:37 +0300 Subject: [PATCH 6/7] fix --- src/util/collections.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/collections.ts b/src/util/collections.ts index a5ea7cc7cb..73876f0aa6 100644 --- a/src/util/collections.ts +++ b/src/util/collections.ts @@ -45,7 +45,7 @@ export class BitSet { add(index: i32): this { var idx = index >>> 5; var words = this.words; - if (idx > words.length) { // resize + if (idx >= words.length) { // resize this.words = new Uint32Array(idx + 16); this.words.set(words); words = this.words; From 70987b0112c08ff0eba30bb12fdefac0f56485e1 Mon Sep 17 00:00:00 2001 From: MaxGraey Date: Tue, 19 Jul 2022 21:06:34 +0300 Subject: [PATCH 7/7] add comment of usage --- src/util/collections.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/util/collections.ts b/src/util/collections.ts index 73876f0aa6..134ea073b0 100644 --- a/src/util/collections.ts +++ b/src/util/collections.ts @@ -25,6 +25,10 @@ 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;