diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index 1248077d1..8d03dcfce 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -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 ) { diff --git a/src/js/strie.js b/src/js/strie.js index 353e4d4ff..599cfd390 100644 --- a/src/js/strie.js +++ b/src/js/strie.js @@ -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 // ***************************************************************************** diff --git a/src/js/utils.js b/src/js/utils.js index 8e2a0f84d..6f3fbc62a 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -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; diff --git a/src/js/wasm/biditrie.wasm b/src/js/wasm/biditrie.wasm new file mode 100644 index 000000000..88367f821 Binary files /dev/null and b/src/js/wasm/biditrie.wasm differ diff --git a/src/js/wasm/biditrie.wat b/src/js/wasm/biditrie.wat new file mode 100644 index 000000000..e5e623349 --- /dev/null +++ b/src/js/wasm/biditrie.wat @@ -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 +;; +)