1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-11-07 03:12:33 +01:00

Add WASM versions for some bidi-trie methods

Related issue:
- https://github.com/uBlockOrigin/uBlock-issues/issues/761

Changes related to above issue made it possible to
create WASM versions of methods used in the bidi-trie.
In this commit, WASM versions for startsWith(), indexOf()
and lastIndexOf() have been implemented.
This commit is contained in:
Raymond Hill 2019-10-26 13:13:53 -04:00
parent dd2a9faa4c
commit b0cbc47d9a
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
5 changed files with 536 additions and 82 deletions

View File

@ -50,6 +50,7 @@ const Important = 1 << 1;
const AnyParty = 0 << 2;
const FirstParty = 1 << 2;
const ThirdParty = 2 << 2;
const BlockImportant = BlockAction | Important;
const typeNameToTypeValue = {
'no_type': 0 << 4,
@ -159,8 +160,6 @@ const toNormalizedType = {
'websocket': 'websocket',
};
const BlockImportant = BlockAction | Important;
const typeValueFromCatBits = catBits => (catBits >>> 4) & 0b11111;
/******************************************************************************/
@ -320,7 +319,11 @@ const bidiTrie = (( ) => {
);
} catch(ex) {
}
return new µb.BidiTrieContainer(trieDetails, bidiTrieMatchExtra);
const trie = new µb.BidiTrieContainer(trieDetails, bidiTrieMatchExtra);
if ( µb.hiddenSettings.disableWebAssembly !== true ) {
trie.enableWASM();
}
return trie;
})();
const bidiTrieOptimize = function() {
@ -498,7 +501,14 @@ const FilterPatternPlain = class {
match() {
const left = $tokenBeg;
if ( bidiTrie.startsWith(left, this.i, this.n) === false ) {
if (
bidiTrie.startsWith(
left,
bidiTrie.haystackLen,
this.i,
this.n
) === 0
) {
return false;
}
$patternMatchLeft = left;
@ -561,7 +571,14 @@ registerFilterClass(FilterPatternPlain);
const FilterPatternPlain1 = class extends FilterPatternPlain {
match() {
const left = $tokenBeg - 1;
if ( bidiTrie.startsWith(left, this.i, this.n) === false ) {
if (
bidiTrie.startsWith(
left,
bidiTrie.haystackLen,
this.i,
this.n
) === 0
) {
return false;
}
$patternMatchLeft = left;
@ -581,7 +598,14 @@ const FilterPatternPlainX = class extends FilterPatternPlain {
match() {
const left = $tokenBeg - this.tokenBeg;
if ( bidiTrie.startsWith(left, this.i, this.n) === false ) {
if (
bidiTrie.startsWith(
left,
bidiTrie.haystackLen,
this.i,
this.n
) === 0
) {
return false;
}
$patternMatchLeft = left;
@ -686,7 +710,7 @@ const FilterPatternRight = class {
match() {
const right = bidiTrie.lastIndexOf(
$patternMatchRight, bidiTrie.haystackSize,
$patternMatchRight, bidiTrie.haystackLen,
this.i, this.n
);
if ( right === -1 ) { return false; }
@ -729,7 +753,7 @@ const FilterPatternRightEx = class extends FilterPatternRight {
match() {
const left = $patternMatchRight;
const right = bidiTrie.lastIndexOf(
left + 1, bidiTrie.haystackSize,
left + 1, bidiTrie.haystackLen,
this.i, this.n
);
if ( right === -1 ) { return false; }
@ -881,7 +905,7 @@ const FilterAnchorHn = class {
this.lastBeg = len !== 0 ? haystackCodes.indexOf(0x3A) : -1;
if ( this.lastBeg !== -1 ) {
if (
this.lastBeg >= bidiTrie.haystackSize ||
this.lastBeg >= bidiTrie.haystackLen ||
haystackCodes[this.lastBeg+1] !== 0x2F ||
haystackCodes[this.lastBeg+2] !== 0x2F
) {

View File

@ -19,6 +19,8 @@
Home: https://github.com/gorhill/uBlock
*/
/* globals WebAssembly */
'use strict';
// *****************************************************************************
@ -131,20 +133,21 @@ const roundToPageSize = v => (v + PAGE_SIZE-1) & ~(PAGE_SIZE-1);
constructor(details, extraHandler) {
if ( details instanceof Object === false ) { details = {}; }
const len = roundToPageSize(details.byteLength || 0);
const minInitialLen = PAGE_SIZE * 4;
this.buf8 = new Uint8Array(Math.max(len, minInitialLen));
const minInitialSize = PAGE_SIZE * 4;
this.buf8 = new Uint8Array(Math.max(len, minInitialSize));
this.buf32 = new Uint32Array(this.buf8.buffer);
this.buf32[TRIE0_SLOT] = TRIE0_START;
this.buf32[TRIE1_SLOT] = this.buf32[TRIE0_SLOT];
this.buf32[CHAR0_SLOT] = details.char0 || (minInitialLen >>> 1);
this.buf32[CHAR0_SLOT] = details.char0 || (minInitialSize >>> 1);
this.buf32[CHAR1_SLOT] = this.buf32[CHAR0_SLOT];
this.haystack = this.buf8.subarray(
HAYSTACK_START,
HAYSTACK_START + HAYSTACK_SIZE
);
this.haystackSize = 0;
this.haystackLen = 0;
this.extraHandler = extraHandler;
this.textDecoder = null;
this.wasmMemory = null;
this.$l = 0;
this.$r = 0;
@ -164,7 +167,7 @@ const roundToPageSize = v => (v + PAGE_SIZE-1) & ~(PAGE_SIZE-1);
const buf32 = this.buf32;
const buf8 = this.buf8;
const char0 = buf32[CHAR0_SLOT];
const aR = this.haystackSize;
const aR = this.haystackLen;
let icell = iroot;
let al = i;
let c, v, bl, n;
@ -180,15 +183,16 @@ const roundToPageSize = v => (v + PAGE_SIZE-1) & ~(PAGE_SIZE-1);
if ( icell === 0 ) { return false; }
}
// all characters in segment must match
n = v >>> 24;
if ( n > 1 ) {
n -= 1;
if ( (al + n) > aR ) { return false; }
bl += 1;
for ( let i = 0; i < n; i++ ) {
if ( buf8[al+i] !== buf8[bl+i] ) { return false; }
}
al += n;
n = (v >>> 24) - 1;
if ( n !== 0 ) {
const ar = al + n;
if ( ar > aR ) { return false; }
let i = al, j = bl + 1;
do {
if ( buf8[i] !== buf8[j] ) { return false; }
i += 1; j += 1;
} while ( i !== ar );
al = i;
}
// next segment
icell = buf32[icell+CELL_AND];
@ -225,20 +229,22 @@ const roundToPageSize = v => (v + PAGE_SIZE-1) & ~(PAGE_SIZE-1);
// find first segment with a first-character match
for (;;) {
v = buf32[icell+SEGMENT_INFO];
n = v >>> 24;
br = char0 + (v & 0x00FFFFFF) + n - 1;
n = (v >>> 24) - 1;
br = char0 + (v & 0x00FFFFFF) + n;
if ( buf8[br] === c ) { break; }
icell = buf32[icell+CELL_OR];
if ( icell === 0 ) { return false; }
}
// all characters in segment must match
if ( n > 1 ) {
n -= 1;
if ( n > ar ) { return false; }
for ( let i = 1; i <= n; i++ ) {
if ( buf8[ar-i] !== buf8[br-i] ) { return false; }
}
ar -= n;
if ( n !== 0 ) {
const al = ar - n;
if ( al < 0 ) { return false; }
let i = ar, j = br;
do {
i -= 1; j -= 1;
if ( buf8[i] !== buf8[j] ) { return false; }
} while ( i !== al );
ar = i;
}
// next segment
icell = buf32[icell+CELL_AND];
@ -605,75 +611,101 @@ const roundToPageSize = v => (v + PAGE_SIZE-1) & ~(PAGE_SIZE-1);
}
// WASMable.
startsWith(haystackOffset, needleOffset, needleLen) {
if ( (haystackOffset + needleLen) > this.haystackSize ) {
return false;
startsWith(haystackLeft, haystackRight, needleLeft, needleLen) {
if ( haystackLeft < 0 || (haystackLeft + needleLen) > haystackRight ) {
return 0;
}
const haystackCodes = this.haystack;
const needleCodes = this.buf8;
needleOffset += this.buf32[CHAR0_SLOT];
for ( let i = 0; i < needleLen; i++ ) {
if (
haystackCodes[haystackOffset+i] !==
needleCodes[needleOffset+i]
) {
return false;
}
const charCodes = this.buf8;
needleLeft += this.buf32[CHAR0_SLOT];
const needleRight = needleLeft + needleLen;
while ( charCodes[haystackLeft] === charCodes[needleLeft] ) {
needleLeft += 1;
if ( needleLeft === needleRight ) { return 1; }
haystackLeft += 1;
}
return true;
return 0;
}
// Find the left-most instance of substring in main string
// WASMable.
indexOf(haystackBeg, haystackEnd, needleOffset, needleLen) {
indexOf(haystackLeft, haystackEnd, needleLeft, needleLen) {
haystackEnd -= needleLen;
if ( haystackEnd < haystackBeg ) { return -1; }
const haystackCodes = this.haystack;
const needleCodes = this.buf8;
needleOffset += this.buf32[CHAR0_SLOT];
let i = haystackBeg;
let c0 = needleCodes[needleOffset];
if ( haystackEnd < haystackLeft ) { return -1; }
needleLeft += this.buf32[CHAR0_SLOT];
const needleRight = needleLeft + needleLen;
const charCodes = this.buf8;
for (;;) {
i = haystackCodes.indexOf(c0, i);
if ( i === -1 || i > haystackEnd ) { return -1; }
let j = 1;
while ( j < needleLen ) {
if ( haystackCodes[i+j] !== needleCodes[needleOffset+j] ) {
break;
}
let i = haystackLeft;
let j = needleLeft;
while ( charCodes[i] === charCodes[j] ) {
j += 1;
if ( j === needleRight ) { return haystackLeft; }
i += 1;
}
if ( j === needleLen ) { return i; }
i += 1;
haystackLeft += 1;
if ( haystackLeft === haystackEnd ) { break; }
}
return -1;
}
// Find the right-most instance of substring in main string.
// WASMable.
lastIndexOf(haystackBeg, haystackEnd, needleOffset, needleLen) {
needleOffset += this.buf32[CHAR0_SLOT];
let i = haystackEnd - needleLen;
if ( i < haystackBeg ) { return -1; }
const haystackCodes = this.haystack;
const needleCodes = this.buf8;
let c0 = needleCodes[needleOffset];
lastIndexOf(haystackBeg, haystackEnd, needleLeft, needleLen) {
let haystackLeft = haystackEnd - needleLen;
if ( haystackLeft < haystackBeg ) { return -1; }
needleLeft += this.buf32[CHAR0_SLOT];
const needleRight = needleLeft + needleLen;
const charCodes = this.buf8;
for (;;) {
i = haystackCodes.lastIndexOf(c0, i);
if ( i === -1 || i < haystackBeg ) { return -1; }
let j = 1;
while ( j < needleLen ) {
if ( haystackCodes[i+j] !== needleCodes[needleOffset+j] ) {
break;
}
let i = haystackLeft;
let j = needleLeft;
while ( charCodes[i] === charCodes[j] ) {
j += 1;
if ( j === needleRight ) { return haystackLeft; }
i += 1;
}
if ( j === needleLen ) { return i; }
i -= 1;
if ( haystackLeft === haystackBeg ) { break; }
haystackLeft -= 1;
}
return -1;
}
async enableWASM() {
if ( this.wasmMemory instanceof WebAssembly.Memory ) { return true; }
const module = await getWasmModule();
if ( module instanceof WebAssembly.Module === false ) {
return false;
}
const memory = new WebAssembly.Memory({
initial: this.buf8.length >>> 16
});
const instance = await WebAssembly.instantiate(
module,
{ imports: { memory } }
);
if ( instance instanceof WebAssembly.Instance === false ) {
return false;
}
this.wasmMemory = memory;
const curPageCount = memory.buffer.byteLength >>> 16;
const newPageCount = this.buf8.byteLength + PAGE_SIZE-1 >>> 16;
if ( newPageCount > curPageCount ) {
memory.grow(newPageCount - curPageCount);
}
const buf8 = new Uint8Array(memory.buffer);
buf8.set(this.buf8);
this.buf8 = buf8;
this.buf32 = new Uint32Array(this.buf8.buffer);
this.haystack = this.buf8.subarray(
HAYSTACK_START,
HAYSTACK_START + HAYSTACK_SIZE
);
this.startsWith = instance.exports.startsWith;
this.indexOf = instance.exports.indexOf;
this.lastIndexOf = instance.exports.lastIndexOf;
return true;
}
//--------------------------------------------------------------------------
// Private methods
//--------------------------------------------------------------------------
@ -710,6 +742,7 @@ const roundToPageSize = v => (v + PAGE_SIZE-1) & ~(PAGE_SIZE-1);
}
shrinkBuf() {
if ( this.wasmMemory !== null ) { return; }
const char0 = this.buf32[TRIE1_SLOT] + MIN_FREE_CELL_BYTE_LENGTH;
const char1 = char0 + this.buf32[CHAR1_SLOT] - this.buf32[CHAR0_SLOT];
const bufLen = char1 + 256;
@ -723,9 +756,25 @@ const roundToPageSize = v => (v + PAGE_SIZE-1) & ~(PAGE_SIZE-1);
}
const charDataLen = this.buf32[CHAR1_SLOT] - this.buf32[CHAR0_SLOT];
if ( bufLen !== this.buf8.length ) {
const newBuf = new Uint8Array(bufLen);
let newBuf;
if ( this.wasmMemory === null ) {
newBuf = new Uint8Array(bufLen);
} else {
const oldPageCount = this.buf8.length >>> 16;
const newPageCount = (bufLen + 0xFFFF) >>> 16;
if ( newPageCount > oldPageCount ) {
this.wasmMemory.grow(newPageCount - oldPageCount);
}
newBuf = new Uint8Array(this.wasmMemory.buffer);
}
newBuf.set(this.buf8.subarray(0, this.buf32[TRIE1_SLOT]), 0);
newBuf.set(this.buf8.subarray(this.buf32[CHAR0_SLOT], this.buf32[CHAR1_SLOT]), char0);
newBuf.set(
this.buf8.subarray(
this.buf32[CHAR0_SLOT],
this.buf32[CHAR1_SLOT]
),
char0
);
this.buf8 = newBuf;
this.buf32 = new Uint32Array(this.buf8.buffer);
this.buf32[CHAR0_SLOT] = char0;
@ -736,7 +785,11 @@ const roundToPageSize = v => (v + PAGE_SIZE-1) & ~(PAGE_SIZE-1);
);
}
if ( char0 !== this.buf32[CHAR0_SLOT] ) {
this.buf8.copyWithin(char0, this.buf32[CHAR0_SLOT], this.buf32[CHAR1_SLOT]);
this.buf8.copyWithin(
char0,
this.buf32[CHAR0_SLOT],
this.buf32[CHAR1_SLOT]
);
this.buf32[CHAR0_SLOT] = char0;
this.buf32[CHAR1_SLOT] = char0 + charDataLen;
}
@ -839,6 +892,72 @@ const roundToPageSize = v => (v + PAGE_SIZE-1) & ~(PAGE_SIZE-1);
}
};
/******************************************************************************/
// Code below is to attempt to load a WASM module which implements:
//
// - BidiTrieContainer.startsWith()
//
// The WASM module is entirely optional, the JS implementations will be
// used should the WASM module be unavailable for whatever reason.
const getWasmModule = (( ) => {
let wasmModulePromise;
return function() {
if ( wasmModulePromise instanceof Promise ) {
return wasmModulePromise;
}
if (
typeof WebAssembly !== 'object' ||
typeof WebAssembly.compileStreaming !== 'function'
) {
return;
}
// Soft-dependency on vAPI so that the code here can be used outside of
// uBO (i.e. tests, benchmarks)
if (
typeof vAPI === 'object' &&
vAPI.webextFlavor.soup.has('firefox') === false
) {
return;
}
// The wasm module will work only if CPU is natively little-endian,
// as we use native uint32 array in our js code.
const uint32s = new Uint32Array(1);
const uint8s = new Uint8Array(uint32s.buffer);
uint32s[0] = 1;
if ( uint8s[0] !== 1 ) { return; }
// The directory from which the current script was fetched should also
// contain the related WASM file. The script is fetched from a trusted
// location, and consequently so will be the related WASM file.
let workingDir;
{
const url = new URL(document.currentScript.src);
const match = /[^\/]+$/.exec(url.pathname);
if ( match !== null ) {
url.pathname = url.pathname.slice(0, match.index);
}
workingDir = url.href;
}
wasmModulePromise = fetch(
workingDir + 'wasm/biditrie.wasm',
{ mode: 'same-origin' }
).then(
WebAssembly.compileStreaming
).catch(reason => {
log.info(reason);
});
return wasmModulePromise;
};
})();
// end of local namespace
// *****************************************************************************

View File

@ -155,7 +155,7 @@
url = url.slice(0, 2048);
l = 2048;
}
encodeInto.haystackSize = l;
encodeInto.haystackLen = l;
const knownTokens = this.knownTokens;
const vtc = this._validTokenChars;
const charCodes = encodeInto.haystack;

BIN
src/js/wasm/biditrie.wasm Normal file

Binary file not shown.

311
src/js/wasm/biditrie.wat Normal file
View File

@ -0,0 +1,311 @@
;;
;; uBlock Origin - a browser extension to block requests.
;; Copyright (C) 2019-present Raymond Hill
;;
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see {http://www.gnu.org/licenses/}.
;;
;; Home: https://github.com/gorhill/uBlock
;; File: biditrie.wat
;; Description: WebAssembly code used by src/js/biditrie.js
;; How to compile: See README.md in this directory.
(module
;;
;; module start
;;
(memory (import "imports" "memory") 1)
;; Trie container
;;
;; Memory layout, byte offset:
;; HAYSTACK_START = 0;
;; HAYSTACK_SIZE = 2048; // i32 / i8
;; HAYSTACK_SIZE_SLOT = HAYSTACK_SIZE >>> 2; // 512 / 2048
;; TRIE0_SLOT = HAYSTACK_SIZE_SLOT + 1; // 512 / 2052
;; TRIE1_SLOT = HAYSTACK_SIZE_SLOT + 2; // 513 / 2056
;; CHAR0_SLOT = HAYSTACK_SIZE_SLOT + 3; // 514 / 2060
;; CHAR1_SLOT = HAYSTACK_SIZE_SLOT + 4; // 515 / 2064
;; TRIE0_START = HAYSTACK_SIZE_SLOT + 5 << 2; // 2068
;;
;;
;; Public functions
;;
;;
;; unsigned int startsWith(haystackLeft, haystackRight, needleLeft, needleLen)
;;
;; Test whether the string at needleOffset and of length needleLen matches
;; the haystack at offset haystackOffset.
;;
(func (export "startsWith")
(param $haystackLeft i32) ;; start offset in haystack
(param $haystackRight i32) ;; end offset in haystack
(param $needleLeft i32) ;; start of needle in character buffer
(param $needleLen i32) ;; number of characters to match
(result i32) ;; result: 0 = no match, 1 = match
(local $needleRight i32)
;;
;; if ( haystackLeft < 0 || (haystackLeft + needleLen) > haystackRight ) {
;; return 0;
;; }
get_local $haystackLeft
i32.const 0
i32.lt_s
if
i32.const 0
return
end
get_local $haystackLeft
get_local $needleLen
i32.add
get_local $haystackRight
i32.gt_u
if
i32.const 0
return
end
;; const charCodes = this.buf8;
;; needleLeft += this.buf32[CHAR0_SLOT];
get_local $needleLeft
i32.const 2060 ;; CHAR0_SLOT memory address
i32.load ;; CHAR0 memory address
i32.add ;; needle memory address
;; const needleRight = needleLeft + needleLen;
tee_local $needleLeft
get_local $needleLen
i32.add
set_local $needleRight
;; while ( charCodes[haystackLeft] === charCodes[needleLeft] ) {
block $breakCompare loop $compare
get_local $haystackLeft
i32.load8_u
get_local $needleLeft
i32.load8_u
i32.ne
br_if $breakCompare
;; needleLeft += 1;
get_local $needleLeft
i32.const 1
i32.add
tee_local $needleLeft
;; if ( needleLeft === needleRight ) { break; }
get_local $needleRight
i32.eq
if
i32.const 1
return
end
;; haystackLeft += 1;
i32.const 1
get_local $haystackLeft
i32.add
set_local $haystackLeft
br $compare
end end
;; }
;; return true;
i32.const 1
)
;;
;; int indexOf(haystackLeft, haystackEnd, needleLeft, needleLen)
;;
;; Test whether the string at needleOffset and of length needleLen is found in
;; the haystack at or to the left of haystackLeft, but not farther than
;; haystackEnd.
;;
(func (export "indexOf")
(param $haystackLeft i32) ;; start offset in haystack
(param $haystackEnd i32) ;; end offset in haystack
(param $needleLeft i32) ;; start of needle in character buffer
(param $needleLen i32) ;; number of characters to match
(result i32) ;; result: index of match, -1 = no match
(local $needleRight i32)
(local $i i32)
(local $j i32)
(local $c0 i32)
;; haystackEnd -= needleLen;
get_local $haystackEnd
get_local $needleLen
i32.sub
tee_local $haystackEnd
;; if ( haystackEnd < haystackLeft ) { return -1; }
get_local $haystackLeft
i32.lt_s
if
i32.const -1
return
end
;; needleLeft += this.buf32[CHAR0_SLOT];
get_local $needleLeft
i32.const 2060 ;; CHAR0_SLOT memory address
i32.load ;; CHAR0 memory address
i32.add ;; needle memory address
tee_local $needleLeft
;; const needleRight = needleLeft + needleLen;
get_local $needleLen
i32.add
set_local $needleRight
;; const charCodes = this.buf8;
;; for (;;) {
block $breakMainLoop loop $mainLoop
;; let i = haystackLeft;
;; let j = needleLeft;
get_local $haystackLeft
set_local $i
get_local $needleLeft
set_local $j
;; while ( charCodes[i] === charCodes[j] ) {
block $breakMatchChars loop $matchChars
get_local $i
i32.load8_u
get_local $j
i32.load8_u
i32.ne
br_if $breakMatchChars
;; j += 1;
get_local $j
i32.const 1
i32.add
tee_local $j
;; if ( j === needleRight ) { return haystackLeft; }
get_local $needleRight
i32.eq
if
get_local $haystackLeft
return
end
;; i += 1;
get_local $i
i32.const 1
i32.add
set_local $i
br $matchChars
;; }
end end
;; haystackLeft += 1;
get_local $haystackLeft
i32.const 1
i32.add
tee_local $haystackLeft
;; if ( haystackLeft === haystackEnd ) { break; }
get_local $haystackEnd
i32.ne
br_if $mainLoop
;; }
end end
;; return -1;
i32.const -1
)
;;
;; int lastIndexOf(haystackBeg, haystackEnd, needleLeft, needleLen)
;;
;; Test whether the string at needleOffset and of length needleLen is found in
;; the haystack at or to the right of haystackBeg, but not farther than
;; haystackEnd.
;;
(func (export "lastIndexOf")
(param $haystackBeg i32) ;; start offset in haystack
(param $haystackEnd i32) ;; end offset in haystack
(param $needleLeft i32) ;; start of needle in character buffer
(param $needleLen i32) ;; number of characters to match
(result i32) ;; result: index of match, -1 = no match
(local $haystackLeft i32)
(local $needleRight i32)
(local $i i32)
(local $j i32)
(local $c0 i32)
;; let haystackLeft = haystackEnd - needleLen;
get_local $haystackEnd
get_local $needleLen
i32.sub
tee_local $haystackLeft
;; if ( haystackLeft < haystackBeg ) { return -1; }
get_local $haystackBeg
i32.lt_s
if
i32.const -1
return
end
;; needleLeft += this.buf32[CHAR0_SLOT];
get_local $needleLeft
i32.const 2060 ;; CHAR0_SLOT memory address
i32.load ;; CHAR0 memory address
i32.add ;; needle memory address
tee_local $needleLeft
;; const needleRight = needleLeft + needleLen;
get_local $needleLen
i32.add
set_local $needleRight
;; const charCodes = this.buf8;
;; for (;;) {
block $breakMainLoop loop $mainLoop
;; let i = haystackLeft;
;; let j = needleLeft;
get_local $haystackLeft
set_local $i
get_local $needleLeft
set_local $j
;; while ( charCodes[i] === charCodes[j] ) {
block $breakMatchChars loop $matchChars
get_local $i
i32.load8_u
get_local $j
i32.load8_u
i32.ne
br_if $breakMatchChars
;; j += 1;
get_local $j
i32.const 1
i32.add
tee_local $j
;; if ( j === needleRight ) { return haystackLeft; }
get_local $needleRight
i32.eq
if
get_local $haystackLeft
return
end
;; i += 1;
get_local $i
i32.const 1
i32.add
set_local $i
br $matchChars
;; }
end end
;; if ( haystackLeft === haystackBeg ) { break; }
;; haystackLeft -= 1;
get_local $haystackLeft
get_local $haystackBeg
i32.eq
br_if $breakMainLoop
get_local $haystackLeft
i32.const 1
i32.sub
set_local $haystackLeft
br $mainLoop
;; }
end end
;; return -1;
i32.const -1
)
;;
;; module end
;;
)