blob: 1cf2d52db462a208682452d8cd3ed00076ecbdc1 [file] [log] [blame]
// Copyright 2017 The Puffs Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
pri func flate_decoder.decode_huffman_slow?(dst writer1, src reader1)() {
// When editing this function, consider making the equivalent change to the
// decode_huffman_fast function. Keep the diff between the two
// decode_huffman_*.puffs files as small as possible, while retaining both
// correctness and performance.
if (this.n_bits >= 8) or ((this.bits >> this.n_bits) != 0) {
return error "internal error: inconsistent n_bits"
}
var bits u32 = this.bits
var n_bits u32 = this.n_bits
var table_entry u32
var table_entry_n_bits u32[..15]
var lmask u32[..511] = ((1 as u32) << this.n_huffs_bits[0]) - 1
var dmask u32[..511] = ((1 as u32) << this.n_huffs_bits[1]) - 1
while:loop true {
// Decode an lcode symbol from H-L.
while true {
table_entry = this.huffs[0][bits & lmask]
table_entry_n_bits = table_entry & 0x0F
if n_bits >= table_entry_n_bits {
bits >>= table_entry_n_bits
n_bits -= table_entry_n_bits
break
}
assert n_bits < 15 via "a < b: a < c; c <= b"(c:table_entry_n_bits)
bits |= (in.src.read_u8?() as u32) << n_bits
n_bits += 8
}
if (table_entry >> 31) != 0 {
// Literal.
in.dst.write_u8?(x:(table_entry >> 8) & 0xFF)
continue:loop
} else if (table_entry >> 30) != 0 {
// No-op; code continues past the if-else chain.
} else if (table_entry >> 29) != 0 {
// End of block.
this.end_of_block = true
break:loop
} else if (table_entry >> 28) != 0 {
// Redirect.
var redir_top u32[..0xFFFF] = (table_entry >> 8) & 0xFFFF
var redir_mask u32[..0x7FFF] = ((1 as u32) << ((table_entry >> 4) & 0x0F)) - 1
while true {
if (redir_top + (bits & redir_mask)) >= 1234 {
return error "internal error: inconsistent Huffman decoder state"
}
table_entry = this.huffs[0][redir_top + (bits & redir_mask)]
table_entry_n_bits = table_entry & 0x0F
if n_bits >= table_entry_n_bits {
bits >>= table_entry_n_bits
n_bits -= table_entry_n_bits
break
}
assert n_bits < 15 via "a < b: a < c; c <= b"(c:table_entry_n_bits)
bits |= (in.src.read_u8?() as u32) << n_bits
n_bits += 8
}
if (table_entry >> 31) != 0 {
// Literal.
in.dst.write_u8?(x:(table_entry >> 8) & 0xFF)
continue:loop
} else if (table_entry >> 30) != 0 {
// No-op; code continues past the if-else chain.
} else if (table_entry >> 29) != 0 {
// End of block.
this.end_of_block = true
break:loop
} else if (table_entry >> 28) != 0 {
return error "internal error: inconsistent Huffman decoder state"
} else if (table_entry >> 27) != 0 {
return error "bad Huffman code"
} else {
return error "internal error: inconsistent Huffman decoder state"
}
} else if (table_entry >> 27) != 0 {
return error "bad Huffman code"
} else {
return error "internal error: inconsistent Huffman decoder state"
}
// length = base number + extra bits.
var length u32[..0x7FFF] = (table_entry >> 8) & 0x7FFF
table_entry_n_bits = (table_entry >> 4) & 0x0F
if table_entry_n_bits > 0 {
while n_bits < table_entry_n_bits,
post n_bits >= table_entry_n_bits,
{
assert n_bits < 15 via "a < b: a < c; c <= b"(c:table_entry_n_bits)
bits |= (in.src.read_u8?() as u32) << n_bits
n_bits += 8
}
length = (length + bits.low_bits(n:table_entry_n_bits)) & 0x7FFF
bits >>= table_entry_n_bits
n_bits -= table_entry_n_bits
}
// Decode a dcode symbol from H-D.
while true {
table_entry = this.huffs[1][bits & dmask]
table_entry_n_bits = table_entry & 15
if n_bits >= table_entry_n_bits {
bits >>= table_entry_n_bits
n_bits -= table_entry_n_bits
break
}
assert n_bits < 15 via "a < b: a < c; c <= b"(c:table_entry_n_bits)
bits |= (in.src.read_u8?() as u32) << n_bits
n_bits += 8
}
// Check for a redirect.
if (table_entry >> 28) == 1 {
redir_top = (table_entry >> 8) & 0xFFFF
redir_mask = ((1 as u32) << ((table_entry >> 4) & 0x0F)) - 1
while true {
if (redir_top + (bits & redir_mask)) >= 1234 {
return error "internal error: inconsistent Huffman decoder state"
}
table_entry = this.huffs[1][redir_top + (bits & redir_mask)]
table_entry_n_bits = table_entry & 0x0F
if n_bits >= table_entry_n_bits {
bits >>= table_entry_n_bits
n_bits -= table_entry_n_bits
break
}
assert n_bits < 15 via "a < b: a < c; c <= b"(c:table_entry_n_bits)
bits |= (in.src.read_u8?() as u32) << n_bits
n_bits += 8
}
}
// For H-D, all symbols should be base number + extra bits.
if (table_entry >> 24) != 0x40 {
if (table_entry >> 24) == 0x08 {
return error "bad Huffman code"
}
return error "internal error: inconsistent Huffman decoder state"
}
// distance = base number + extra bits.
var distance u32[..0x7FFF] = (table_entry >> 8) & 0x7FFF
table_entry_n_bits = (table_entry >> 4) & 0x0F
if table_entry_n_bits > 0 {
while n_bits < table_entry_n_bits,
post n_bits >= table_entry_n_bits,
{
assert n_bits < 15 via "a < b: a < c; c <= b"(c:table_entry_n_bits)
bits |= (in.src.read_u8?() as u32) << n_bits
n_bits += 8
}
distance = (distance + bits.low_bits(n:table_entry_n_bits)) & 0x7FFF
bits >>= table_entry_n_bits
n_bits -= table_entry_n_bits
}
var n_copied u32
while true {
// Copy from this.history.
if (distance as u64) > in.dst.since_mark().length() {
// Set (hlen, hdist) to be the length-distance pair to copy
// from this.history, and (length, distance) to be the
// remaining length-distance pair to copy from in.dst.
var hlen u32[..0x7FFF]
var hdist u32[..0x7FFF] = ((distance as u64) - in.dst.since_mark().length()) as u32
if length > hdist {
length -= hdist
hlen = hdist
} else {
hlen = length
length = 0
}
if this.history_index < hdist {
return error "bad distance"
}
// Re-purpose the hdist variable as the this.history index to
// start copying from.
hdist = (this.history_index - hdist) & 0x7FFF
// Copy from hdist to the end of this.history.
while true {
n_copied = in.dst.copy_from_slice32(s:this.history[hdist:], length:hlen)
if hlen <= n_copied {
hlen = 0
break
}
if n_copied > 0 {
hlen -= n_copied
hdist = (hdist ~+ n_copied) & 0x7FFF
if hdist == 0 {
// Wrap around the this.history ringbuffer.
break
}
}
// TODO: "closed for writes" instead?
return suspension "short write"
// TODO: this "assert false" should be a compiler error.
// This might mean differentiating "return" and "raise".
//
// TODO: invalidate this-related facts because the
// suspension can change the state of this and its fields.
assert false
}
// Copy from the start of this.history, if we wrapped around.
if hlen > 0 {
while true {
n_copied = in.dst.copy_from_slice32(s:this.history[hdist:], length:hlen)
if hlen <= n_copied {
hlen = 0
break
}
hlen -= n_copied
hdist = (hdist + (n_copied & 0x7FFF)) & 0x7FFF
// TODO: "closed for writes" instead?
return suspension "short write"
}
}
if length == 0 {
// No need to copy from in.dst.
continue:loop
}
}
// Copy from in.dst.
n_copied = in.dst.copy_from_history32(distance:distance, length:length)
if length <= n_copied {
length = 0
break
}
length -= n_copied
// TODO: "closed for writes" instead?
return suspension "short write"
}
}
// TODO: "assert n_bits < 8"? What about (bits >> n_bits)?
this.bits = bits
this.n_bits = n_bits
if (this.n_bits >= 8) or ((this.bits >> this.n_bits) != 0) {
return error "internal error: inconsistent n_bits"
}
}