diff --git a/src/crc32.ts b/src/crc32.ts index 7bdb6bb..1918e57 100644 --- a/src/crc32.ts +++ b/src/crc32.ts @@ -1,12 +1,30 @@ -import { makeUint8Array } from "./utils.ts" +import { makeUint8Array, parseWasm } from "./utils.ts" -const wasm = "AGFzbQEAAAABCgJgAABgAn9/AXwDAwIAAQUDAQACBw0DAW0CAAF0AAABYwABCpUBAkkBA38DQCABIQBBACECA0AgAEEBdiAAQQFxQaCG4u1+bHMhACACQQFqIgJBCEcNAAsgAUECdCAANgIAIAFBAWoiAUGAAkcNAAsLSQEBfyABQX9zIQFBgIAEIQJBgIAEIABqIQADQCABQf8BcSACLQAAc0ECdCgCACABQQh2cyEBIAJBAWoiAiAASQ0ACyABQX9zuAs" +const head = "AGFzbQEAAAAB" +const test = "BQFgAAF7AwIBAAoKAQgAQQD9D/1iCw" +const defBasic = "CwJgAABgA39/fwF8AwMCAAEFAwEAAgcNAwFtAgABdAAAAWMAAQqWAQI" +const defSIMD = "EQNgAABgA39/fwF8YAJ/fwF8AwUEAAEAAgUDAQACBxEEAW0CAAF0AAABeAACAWMAAwqjAwQ" +const implBasic = "SQEDfwNAIAEhAEEAIQIDQCAAQQF2IABBAXFBoIbi7X5scyEAIAJBAWoiAkEIRw0ACyABQQJ0IAA2AgAgAUEBaiIBQYACRw0ACwtKACABQX9zIQFBgIAEIAJyIQJBgIAEIABqIQADQCABQf8BcSACLQAAc0ECdCgCACABQQh2cyEBIAJBAWoiAiAASQ0ACyABQX9zuAs" +const implSIMD = "TwEDfwNAQQAhAgNAIAIgAXIoAgAiAEH/AXFBAnQoAgAgAEEIdnMhACACQYAIaiICIAFyIAA2AgAgAkGAGEcNAAsgAUEEaiIBQYAIRw0ACwu7AQICfwF7IAFBf3MhAUGAgAQhAkGAgAQgAGoiAEECdUECdCIDQYSABE8EQANAIAEgAigCAHP9Ef0MA////wL///8B////AP////0OQQL9qwH9DAAAAAAABAAAAAgAAAAMAAD9UCIE/RsAKAIAIAT9GwEoAgBzIAT9GwIoAgBzIAT9GwMoAgBzIQEgAkEEaiICIANHDQALCyACIABJBHwgAEGAgARrIAFBf3MgAkGAgARrEAEFIAFBf3O4Cws" + +let enableSIMD = true +try { + parseWasm([head, test]) +} catch(_) { + enableSIMD = false +} + +const instance = new WebAssembly.Instance(parseWasm( + [head, enableSIMD ? defSIMD : defBasic, implBasic, enableSIMD ? implSIMD : ""] +)) +const { t, c, x, m } = instance.exports as { + t(): void, x(): void, m: WebAssembly.Memory + c(length: number, init: number): number +} + +t() // initialize the table of precomputed CRCs ; this takes 1 kB in the first page of Memory +enableSIMD && x() // extend the table of precomputed CRCs ; this takes another 3 kB -const instance = new WebAssembly.Instance( - new WebAssembly.Module(Uint8Array.from(atob(wasm), c => c.charCodeAt(0))) -) -const { t, c, m } = instance.exports as { t(): void, c(length: number, init: number): number, m: WebAssembly.Memory } -t() // initialize the table of precomputed CRCs ; this takes 8 kB in the second page of Memory export const memory = m // for testing // Someday we'll have BYOB stream readers and encodeInto etc. diff --git a/src/crc32.wat b/src/crc32.wat index 78e7593..713ff11 100644 --- a/src/crc32.wat +++ b/src/crc32.wat @@ -23,10 +23,11 @@ ) ;; this computes the CRC32 of what you put in the module's second page of Memory ;; (do not overwrite the first page!) - (func $crc32 (param $len i32) (param $crc i32) (result f64) (local $i i32) + (func $crc32 (param $len i32) (param $crc i32) (param $i i32) (result f64) (local.set $crc (i32.xor (local.get $crc) (i32.const -1))) - (local.set $i (i32.const 0x10000)) + (local.set $i (i32.or (i32.const 0x10000) (local.get $i))) (local.set $len (i32.add (i32.const 0x10000) (local.get $len))) + (loop (i32.and (local.get $crc) (i32.const 0xFF)) (i32.load8_u (local.get $i)) @@ -42,6 +43,86 @@ (i32.xor (local.get $crc) (i32.const -1)) f64.convert_i32_u ;; return a positive Number ) + (func $extendTable (local $crc i32) (local $i i32) (local $j i32) + (loop + (local.set $j (i32.const 0)) + (loop + (i32.load (i32.or (local.get $j) (local.get $i))) + local.tee $crc + + (i32.and (i32.const 0xFF)) + (i32.shl (i32.const 2)) + i32.load + + (i32.shr_u (local.get $crc) (i32.const 8)) + (local.set $crc (i32.xor)) + + (local.tee $j (i32.add (local.get $j) (i32.const 0x400))) + (i32.or (local.get $i)) + + local.get $crc + i32.store + (br_if 0 (i32.ne (local.get $j) (i32.const 0xC00))) + ) + (local.tee $i (i32.add (local.get $i) (i32.const 4))) + (br_if 0 (i32.ne (i32.const 0x400))) + ) ;; in total, the tables occupy the first 4 kB of the first mem page + ) + (func $crc32x4 (param $len i32) (param $crc i32) (result f64) (local $i i32) (local $l8 i32) (local $v v128) + (local.set $crc (i32.xor (local.get $crc) (i32.const -1))) + (local.set $i (i32.const 0x10000)) + (local.tee $len (i32.add (i32.const 0x10000) (local.get $len))) + i32.const 2 + i32.shr_s + i32.const 2 + i32.shl + local.tee $l8 + + (if (i32.ge_u (i32.const 0x10004)) + (loop + (i32.xor (local.get $crc) (i32.load (local.get $i))) + + i32x4.splat + v128.const i32x4 0xFFFFFF03 0xFFFFFF02 0xFFFFFF01 0xFFFFFF00 + i8x16.swizzle ;; this was called 'v8x16.swizzle' if you have an older WABT + + (i32x4.shl (i32.const 2)) + v128.const i32x4 0x00000000 0x00000400 0x00000800 0x00000C00 + v128.or + local.tee $v + (i32.load (i32x4.extract_lane 0)) + + local.get $v + (i32.load (i32x4.extract_lane 1)) + i32.xor + + local.get $v + (i32.load (i32x4.extract_lane 2)) + i32.xor + + local.get $v + (i32.load (i32x4.extract_lane 3)) + i32.xor + + local.set $crc ;; store the updated CRC + (local.tee $i (i32.add (local.get $i) (i32.const 4))) + (br_if 0 (i32.ne (local.get $l8))) + )) + + (if (result f64) (i32.lt_u (local.get $i) (local.get $len)) + (then + (call $crc32 + (i32.sub (local.get $len) (i32.const 0x10000)) + (i32.xor (local.get $crc) (i32.const -1)) + (i32.sub (local.get $i) (i32.const 0x10000)) + ) + ) (else + (i32.xor (local.get $crc) (i32.const -1)) + f64.convert_i32_u ;; return a positive Number + )) + ) (export "t" (func $genTable)) - (export "c" (func $crc32)) -) \ No newline at end of file + ;; (export "c" (func $crc32)) + (export "c" (func $crc32x4)) + (export "x" (func $extendTable)) +) diff --git a/src/utils.ts b/src/utils.ts index 43aa7ed..7e9f8e8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,3 +2,8 @@ export const makeBuffer = (size: number) => new DataView(new ArrayBuffer(size)) export const makeUint8Array = (thing: any) => new Uint8Array(thing.buffer || thing) export const clampInt32 = (n: bigint) => Math.min(0xffffffff, Number(n)) export const clampInt16 = (n: bigint) => Math.min(0xffff, Number(n)) + +export const parseWasm = (base64: string[]) => { + const chunks = base64.flatMap(s => Array.from(atob(s), c => c.charCodeAt(0))) + return new WebAssembly.Module(Uint8Array.of(...chunks)) +} diff --git a/test/crc32.test.ts b/test/crc32.test.ts index d28c4e1..3b805c5 100644 --- a/test/crc32.test.ts +++ b/test/crc32.test.ts @@ -4,8 +4,8 @@ import { crc32, memory } from "../src/crc32.ts" const table = await Deno.readFile("./test/table.array") Deno.test("the CRC32 module precomputes CRCs for each byte using the polynomial 0xEDB88320", () => { - const actual = new Uint8Array(memory.buffer.slice(0, 0x0400)) - const expected = table.slice(0, 0x400) + const actual = new Uint8Array(memory.buffer).subarray(0, 0x1000) + const expected = table.subarray(0, 0x1000) assertEquals(actual, expected) })