summaryrefslogtreecommitdiffstats
path: root/src/lib/lz4/lz4-block-codec.wat
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/lz4/lz4-block-codec.wat')
-rw-r--r--src/lib/lz4/lz4-block-codec.wat745
1 files changed, 745 insertions, 0 deletions
diff --git a/src/lib/lz4/lz4-block-codec.wat b/src/lib/lz4/lz4-block-codec.wat
new file mode 100644
index 0000000..4d9cf42
--- /dev/null
+++ b/src/lib/lz4/lz4-block-codec.wat
@@ -0,0 +1,745 @@
+;;
+;; lz4-block-codec.wat: a WebAssembly implementation of LZ4 block format codec
+;; Copyright (C) 2018 Raymond Hill
+;;
+;; BSD-2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
+;;
+;; Redistribution and use in source and binary forms, with or without
+;; modification, are permitted provided that the following conditions are
+;; met:
+;;
+;; 1. Redistributions of source code must retain the above copyright
+;; notice, this list of conditions and the following disclaimer.
+;;
+;; 2. Redistributions in binary form must reproduce the above
+;; copyright notice, this list of conditions and the following disclaimer
+;; in the documentation and/or other materials provided with the
+;; distribution.
+;;
+;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+;; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+;; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+;;
+;; Home: https://github.com/gorhill/lz4-wasm
+;;
+;; I used the same license as the one picked by creator of LZ4 out of respect
+;; for his creation, see https://lz4.github.io/lz4/
+;;
+
+(module
+;;
+;; module start
+;;
+
+;; (func $log (import "imports" "log") (param i32 i32 i32))
+
+(memory (export "memory") 1)
+
+;;
+;; Public functions
+;;
+
+;;
+;; Return an offset to the first byte of usable linear memory.
+;; Might be useful in the future to reserve memory space for whatever purpose,
+;; like config variables, etc.
+;;
+(func $getLinearMemoryOffset (export "getLinearMemoryOffset")
+ (result i32)
+ i32.const 0
+)
+
+;;
+;; unsigned int lz4BlockEncodeBound()
+;;
+;; Return the maximum size of the output buffer holding the compressed data.
+;;
+;; Reference implementation:
+;; https://github.com/lz4/lz4/blob/dev/lib/lz4.h#L156
+;;
+(func (export "lz4BlockEncodeBound")
+ (param $ilen i32)
+ (result i32)
+ local.get $ilen
+ i32.const 0x7E000000
+ i32.gt_u
+ if
+ i32.const 0
+ return
+ end
+ local.get $ilen
+ local.get $ilen
+ i32.const 255
+ i32.div_u
+ i32.add
+ i32.const 16
+ i32.add
+)
+
+;;
+;; unsigned int lz4BlockEncode(
+;; unsigned int inPtr,
+;; unsigned int ilen,
+;; unsigned int outPtr
+;; )
+;;
+;; https://github.com/lz4/lz4/blob/dev/lib/lz4.c#L651
+;;
+;; The implementation below is modified from the reference one.
+;;
+;; - There is no skip adjustment for repeated failure to find a match.
+;;
+;; - All configurable values are hard-coded to match the generic version
+;; of the compressor.
+;;
+;; Note the size of the input block is NOT encoded in the output buffer, it
+;; is for the caller to figure how they will save that information on
+;; their side. At this point it is probably a trivial amount of work to
+;; implement the LZ4 frame format, which encode the content size, but this
+;; is for another day.
+;;
+(func $lz4BlockEncode (export "lz4BlockEncode")
+ (param $inPtr i32) ;; pointer to start of input buffer
+ (param $ilen i32) ;; size of input buffer
+ (param $outPtr i32) ;; pointer to start of output buffer
+ (result i32)
+ (local $hashPtrBeg i32) ;; start of hash buffer
+ (local $hashPtr i32) ;; current hash entry
+ (local $anchorPtr i32) ;; anchor position in input
+ (local $inPtrEnd1 i32) ;; point in input at which match-finding must cease
+ (local $inPtrEnd2 i32) ;; point in input at which match-length finding must cease
+ (local $inPtrEnd i32) ;; point to end of input
+ (local $outPtrBeg i32) ;; start of output buffer
+ (local $refPtr i32) ;; start of match in input
+ (local $seq32 i32) ;; 4-byte value from current input position
+ (local $llen i32) ;; length of found literals
+ (local $moffset i32) ;; offset to found match from current input position
+ (local $mlen i32) ;; length of found match
+ local.get $ilen ;; empty input = empty output
+ i32.const 0x7E000000 ;; max input size: 0x7E000000
+ i32.gt_u
+ if
+ i32.const 0
+ return
+ end
+ local.get $ilen ;; "blocks < 13 bytes cannot be compressed"
+ i32.const 13
+ i32.lt_u
+ if
+ i32.const 0
+ return
+ end
+ call $getLinearMemoryOffset ;; hash table is at start of usable memory
+ local.set $hashPtrBeg
+ local.get $inPtr
+ local.tee $anchorPtr
+ local.get $ilen
+ i32.add
+ local.tee $inPtrEnd
+ i32.const -5 ;; "The last 5 bytes are always literals."
+ i32.add
+ local.tee $inPtrEnd2
+ i32.const -7 ;; "The last match must start at least 12 bytes before end of block"
+ i32.add
+ local.set $inPtrEnd1
+ local.get $outPtr
+ local.set $outPtrBeg
+ ;;
+ ;; sequence processing loop
+ ;;
+ block $noMoreSequence loop $nextSequence
+ local.get $inPtr
+ local.get $inPtrEnd1
+ i32.ge_u ;; 5 or less bytes left?
+ br_if $noMoreSequence
+ local.get $inPtr ;; first sequence of 3 bytes before match-finding loop
+ i32.load8_u
+ i32.const 8
+ i32.shl
+ local.get $inPtr
+ i32.load8_u offset=1
+ i32.const 16
+ i32.shl
+ i32.or
+ local.get $inPtr
+ i32.load8_u offset=2
+ i32.const 24
+ i32.shl
+ i32.or
+ local.set $seq32
+ ;;
+ ;; match-finding loop
+ ;;
+ loop $findMatch block $noMatchFound
+ local.get $inPtr
+ local.get $inPtrEnd2
+ i32.gt_u ;; less than 12 bytes left?
+ br_if $noMoreSequence
+ local.get $seq32 ;; update last byte of current sequence
+ i32.const 8
+ i32.shr_u
+ local.get $inPtr
+ i32.load8_u offset=3
+ i32.const 24
+ i32.shl
+ i32.or
+ local.tee $seq32
+ i32.const 0x9E3779B1 ;; compute 16-bit hash
+ i32.mul
+ i32.const 16
+ i32.shr_u ;; hash value is at top of stack
+ i32.const 2 ;; lookup refPtr at hash entry
+ i32.shl
+ local.get $hashPtrBeg
+ i32.add
+ local.tee $hashPtr
+ i32.load
+ local.set $refPtr
+ local.get $hashPtr ;; update hash entry with inPtr
+ local.get $inPtr
+ i32.store
+ local.get $inPtr
+ local.get $refPtr
+ i32.sub
+ local.tee $moffset ;; remember match offset, we will need it in case of match
+ i32.const 0xFFFF
+ i32.gt_s ;; match offset > 65535 = unusable match
+ br_if $noMatchFound
+ ;;
+ ;; confirm match: different sequences can yield same hash
+ ;; compare-branch each byte to potentially save memory read ops
+ ;;
+ local.get $seq32 ;; byte 0
+ i32.const 0xFF
+ i32.and
+ local.get $refPtr
+ i32.load8_u
+ i32.ne ;; refPtr[0] !== inPtr[0]
+ br_if $noMatchFound
+ local.get $seq32 ;; byte 1
+ i32.const 8
+ i32.shr_u
+ i32.const 0xFF
+ i32.and
+ local.get $refPtr
+ i32.load8_u offset=1
+ i32.ne
+ br_if $noMatchFound ;; refPtr[1] !== inPtr[1]
+ local.get $seq32 ;; byte 2
+ i32.const 16
+ i32.shr_u
+ i32.const 0xFF
+ i32.and
+ local.get $refPtr
+ i32.load8_u offset=2
+ i32.ne ;; refPtr[2] !== inPtr[2]
+ br_if $noMatchFound
+ local.get $seq32 ;; byte 3
+ i32.const 24
+ i32.shr_u
+ i32.const 0xFF
+ i32.and
+ local.get $refPtr
+ i32.load8_u offset=3
+ i32.ne ;; refPtr[3] !== inPtr[3]
+ br_if $noMatchFound
+ ;;
+ ;; a valid match has been found at this point
+ ;;
+ local.get $inPtr ;; compute length of literals
+ local.get $anchorPtr
+ i32.sub
+ local.set $llen
+ local.get $inPtr ;; find match length
+ i32.const 4 ;; skip over confirmed 4-byte match
+ i32.add
+ local.set $inPtr
+ local.get $refPtr
+ i32.const 4
+ i32.add
+ local.tee $mlen ;; remember refPtr to later compute match length
+ local.set $refPtr
+ block $endOfMatch loop ;; scan input buffer until match ends
+ local.get $inPtr
+ local.get $inPtrEnd2
+ i32.ge_u
+ br_if $endOfMatch
+ local.get $inPtr
+ i32.load8_u
+ local.get $refPtr
+ i32.load8_u
+ i32.ne
+ br_if $endOfMatch
+ local.get $inPtr
+ i32.const 1
+ i32.add
+ local.set $inPtr
+ local.get $refPtr
+ i32.const 1
+ i32.add
+ local.set $refPtr
+ br 0
+ end end $endOfMatch
+ ;; encode token
+ local.get $outPtr ;; output token
+ local.get $llen
+ local.get $refPtr
+ local.get $mlen
+ i32.sub
+ local.tee $mlen
+ call $writeToken
+ local.get $outPtr
+ i32.const 1
+ i32.add
+ local.set $outPtr
+ local.get $llen ;; encode/write length of literals if needed
+ i32.const 15
+ i32.ge_s
+ if
+ local.get $outPtr
+ local.get $llen
+ call $writeLength
+ local.set $outPtr
+ end
+ ;; copy literals
+ local.get $outPtr
+ local.get $anchorPtr
+ local.get $llen
+ call $copy
+ local.get $outPtr
+ local.get $llen
+ i32.add
+ local.set $outPtr
+ ;; encode match offset
+ local.get $outPtr
+ local.get $moffset
+ i32.store8
+ local.get $outPtr
+ local.get $moffset
+ i32.const 8
+ i32.shr_u
+ i32.store8 offset=1
+ local.get $outPtr
+ i32.const 2
+ i32.add
+ local.set $outPtr
+ local.get $mlen ;; encode/write length of match if needed
+ i32.const 15
+ i32.ge_s
+ if
+ local.get $outPtr
+ local.get $mlen
+ call $writeLength
+ local.set $outPtr
+ end
+ local.get $inPtr ;; advance anchor to current position
+ local.set $anchorPtr
+ br $nextSequence
+ end $noMatchFound
+ local.get $inPtr ;; no match found: advance to next byte
+ i32.const 1
+ i32.add
+ local.set $inPtr
+ br $findMatch end ;; match offset > 65535 = unusable match
+ end end $noMoreSequence
+ ;;
+ ;; generate last (match-less) sequence if compression succeeded
+ ;;
+ local.get $outPtr
+ local.get $outPtrBeg
+ i32.eq
+ if
+ i32.const 0
+ return
+ end
+ local.get $outPtr
+ local.get $inPtrEnd
+ local.get $anchorPtr
+ i32.sub
+ local.tee $llen
+ i32.const 0
+ call $writeToken
+ local.get $outPtr
+ i32.const 1
+ i32.add
+ local.set $outPtr
+ local.get $llen
+ i32.const 15
+ i32.ge_u
+ if
+ local.get $outPtr
+ local.get $llen
+ call $writeLength
+ local.set $outPtr
+ end
+ local.get $outPtr
+ local.get $anchorPtr
+ local.get $llen
+ call $copy
+ local.get $outPtr ;; return number of written bytes
+ local.get $llen
+ i32.add
+ local.get $outPtrBeg
+ i32.sub
+)
+
+;;
+;; unsigned int lz4BlockDecode(
+;; unsigned int inPtr,
+;; unsigned int ilen
+;; unsigned int outPtr
+;; )
+;;
+;; Reference:
+;; https://github.com/lz4/lz4/blob/dev/doc/lz4_Block_format.md
+;;
+(func (export "lz4BlockDecode")
+ (param $inPtr0 i32) ;; start of input buffer
+ (param $ilen i32) ;; length of input buffer
+ (param $outPtr0 i32) ;; start of output buffer
+ (result i32)
+ (local $inPtr i32) ;; current position in input buffer
+ (local $inPtrEnd i32) ;; end of input buffer
+ (local $outPtr i32) ;; current position in output buffer
+ (local $matchPtr i32) ;; position of current match
+ (local $token i32) ;; sequence token
+ (local $clen i32) ;; number of bytes to copy
+ (local $_ i32) ;; general purpose variable
+ local.get $ilen ;; if ( ilen == 0 ) { return 0; }
+ i32.eqz
+ if
+ i32.const 0
+ return
+ end
+ local.get $inPtr0
+ local.tee $inPtr ;; current position in input buffer
+ local.get $ilen
+ i32.add
+ local.set $inPtrEnd
+ local.get $outPtr0 ;; start of output buffer
+ local.set $outPtr ;; current position in output buffer
+ block $noMoreSequence loop ;; iterate through all sequences
+ local.get $inPtr
+ local.get $inPtrEnd
+ i32.ge_u
+ br_if $noMoreSequence ;; break when nothing left to read in input buffer
+ local.get $inPtr ;; read token -- consume one byte
+ i32.load8_u
+ local.get $inPtr
+ i32.const 1
+ i32.add
+ local.set $inPtr
+ local.tee $token ;; extract length of literals from token
+ i32.const 4
+ i32.shr_u
+ local.tee $clen ;; consume extra length bytes if present
+ i32.eqz
+ if else
+ local.get $clen
+ i32.const 15
+ i32.eq
+ if loop
+ local.get $inPtr
+ i32.load8_u
+ local.get $inPtr
+ i32.const 1
+ i32.add
+ local.set $inPtr
+ local.tee $_
+ local.get $clen
+ i32.add
+ local.set $clen
+ local.get $_
+ i32.const 255
+ i32.eq
+ br_if 0
+ end end
+ local.get $outPtr ;; copy literals to output buffer
+ local.get $inPtr
+ local.get $clen
+ call $copy
+ local.get $outPtr ;; advance output buffer pointer past copy
+ local.get $clen
+ i32.add
+ local.set $outPtr
+ local.get $clen ;; advance input buffer pointer past literals
+ local.get $inPtr
+ i32.add
+ local.tee $inPtr
+ local.get $inPtrEnd ;; exit if this is the last sequence
+ i32.eq
+ br_if $noMoreSequence
+ end
+ local.get $outPtr ;; read match offset
+ local.get $inPtr
+ i32.load8_u
+ local.get $inPtr
+ i32.load8_u offset=1
+ i32.const 8
+ i32.shl
+ i32.or
+ i32.sub
+ local.tee $matchPtr
+ local.get $outPtr ;; match position can't be outside input buffer bounds
+ i32.eq
+ br_if $noMoreSequence
+ local.get $matchPtr
+ local.get $inPtrEnd
+ i32.lt_u
+ br_if $noMoreSequence
+ local.get $inPtr ;; advance input pointer past match offset bytes
+ i32.const 2
+ i32.add
+ local.set $inPtr
+ local.get $token ;; extract length of match from token
+ i32.const 15
+ i32.and
+ i32.const 4
+ i32.add
+ local.tee $clen
+ i32.const 19 ;; consume extra length bytes if present
+ i32.eq
+ if loop
+ local.get $inPtr
+ i32.load8_u
+ local.get $inPtr
+ i32.const 1
+ i32.add
+ local.set $inPtr
+ local.tee $_
+ local.get $clen
+ i32.add
+ local.set $clen
+ local.get $_
+ i32.const 255
+ i32.eq
+ br_if 0
+ end end
+ local.get $outPtr ;; copy match to output buffer
+ local.get $matchPtr
+ local.get $clen
+ call $copy
+ local.get $clen ;; advance output buffer pointer past copy
+ local.get $outPtr
+ i32.add
+ local.set $outPtr
+ br 0
+ end end $noMoreSequence
+ local.get $outPtr ;; return number of written bytes
+ local.get $outPtr0
+ i32.sub
+)
+
+;;
+;; Private functions
+;;
+
+;;
+;; Encode a sequence token
+;;
+;; Reference documentation:
+;; https://github.com/lz4/lz4/blob/dev/doc/lz4_Block_format.md
+;;
+(func $writeToken
+ (param $outPtr i32)
+ (param $llen i32)
+ (param $mlen i32)
+ local.get $outPtr
+ local.get $llen
+ i32.const 15
+ local.get $llen
+ i32.const 15
+ i32.lt_u
+ select
+ i32.const 4
+ i32.shl
+ local.get $mlen
+ i32.const 15
+ local.get $mlen
+ i32.const 15
+ i32.lt_u
+ select
+ i32.or
+ i32.store8
+)
+
+;;
+;; Encode and output length bytes. The return value is the pointer following
+;; the last byte written.
+;;
+;; Reference documentation:
+;; https://github.com/lz4/lz4/blob/dev/doc/lz4_Block_format.md
+;;
+(func $writeLength
+ (param $outPtr i32)
+ (param $len i32)
+ (result i32)
+ local.get $len
+ i32.const 15
+ i32.sub
+ local.set $len
+ loop
+ local.get $outPtr
+ local.get $len
+ i32.const 255
+ local.get $len
+ i32.const 255
+ i32.lt_u
+ select
+ i32.store8
+ local.get $outPtr
+ i32.const 1
+ i32.add
+ local.set $outPtr
+ local.get $len
+ i32.const 255
+ i32.sub
+ local.tee $len
+ i32.const 0
+ i32.ge_s
+ br_if 0
+ end
+ local.get $outPtr
+)
+
+;;
+;; Copy n bytes from source to destination.
+;;
+;; It is overlap-safe only from left-to-right -- which is only what is
+;; required in the current module.
+;;
+(func $copy
+ (param $dst i32)
+ (param $src i32)
+ (param $len i32)
+ block $lessThan8 loop
+ local.get $len
+ i32.const 8
+ i32.lt_u
+ br_if $lessThan8
+ local.get $dst
+ local.get $src
+ i32.load8_u
+ i32.store8
+ local.get $dst
+ local.get $src
+ i32.load8_u offset=1
+ i32.store8 offset=1
+ local.get $dst
+ local.get $src
+ i32.load8_u offset=2
+ i32.store8 offset=2
+ local.get $dst
+ local.get $src
+ i32.load8_u offset=3
+ i32.store8 offset=3
+ local.get $dst
+ local.get $src
+ i32.load8_u offset=4
+ i32.store8 offset=4
+ local.get $dst
+ local.get $src
+ i32.load8_u offset=5
+ i32.store8 offset=5
+ local.get $dst
+ local.get $src
+ i32.load8_u offset=6
+ i32.store8 offset=6
+ local.get $dst
+ local.get $src
+ i32.load8_u offset=7
+ i32.store8 offset=7
+ local.get $dst
+ i32.const 8
+ i32.add
+ local.set $dst
+ local.get $src
+ i32.const 8
+ i32.add
+ local.set $src
+ local.get $len
+ i32.const -8
+ i32.add
+ local.set $len
+ br 0
+ end end $lessThan8
+ local.get $len
+ i32.const 4
+ i32.ge_u
+ if
+ local.get $dst
+ local.get $src
+ i32.load8_u
+ i32.store8
+ local.get $dst
+ local.get $src
+ i32.load8_u offset=1
+ i32.store8 offset=1
+ local.get $dst
+ local.get $src
+ i32.load8_u offset=2
+ i32.store8 offset=2
+ local.get $dst
+ local.get $src
+ i32.load8_u offset=3
+ i32.store8 offset=3
+ local.get $dst
+ i32.const 4
+ i32.add
+ local.set $dst
+ local.get $src
+ i32.const 4
+ i32.add
+ local.set $src
+ local.get $len
+ i32.const -4
+ i32.add
+ local.set $len
+ end
+ local.get $len
+ i32.const 2
+ i32.ge_u
+ if
+ local.get $dst
+ local.get $src
+ i32.load8_u
+ i32.store8
+ local.get $dst
+ local.get $src
+ i32.load8_u offset=1
+ i32.store8 offset=1
+ local.get $dst
+ i32.const 2
+ i32.add
+ local.set $dst
+ local.get $src
+ i32.const 2
+ i32.add
+ local.set $src
+ local.get $len
+ i32.const -2
+ i32.add
+ local.set $len
+ end
+ local.get $len
+ i32.eqz
+ if else
+ local.get $dst
+ local.get $src
+ i32.load8_u
+ i32.store8
+ end
+)
+
+;;
+;; module end
+;;
+)