Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 25 additions & 7 deletions src/crc32.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
89 changes: 85 additions & 4 deletions src/crc32.wat
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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))
)
;; (export "c" (func $crc32))
(export "c" (func $crc32x4))
(export "x" (func $extendTable))
)
5 changes: 5 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
4 changes: 2 additions & 2 deletions test/crc32.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})

Expand Down