From b1cb937bd6d5669d6c121e7492693ca1dd84b00a Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Wed, 22 May 2019 20:44:08 +0200 Subject: [PATCH 01/54] deflate: Gap hashing and skip encoding Refactor level 1+2 to skip more checks, but do intermediate hashes and extend matches backwards. Same or better compression at same or better speed. --- flate/snappy.go | 187 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 153 insertions(+), 34 deletions(-) diff --git a/flate/snappy.go b/flate/snappy.go index d853320a75..0fbf05dec3 100644 --- a/flate/snappy.go +++ b/flate/snappy.go @@ -120,30 +120,80 @@ func (e *snappyL1) Encode(dst *tokens, src []byte) { // The "skip" variable keeps track of how many bytes there are since // the last match; dividing it by 32 (ie. right-shifting by five) gives // the number of bytes to move ahead for each iteration. - skip := 32 - + const skipLog = 5 + const baseSkip = 1 nextS := s candidate := 0 for { s = nextS - bytesBetweenHashLookups := skip >> 5 - nextS = s + bytesBetweenHashLookups - skip += bytesBetweenHashLookups + nextS = (s + baseSkip) + (s-nextEmit)>>skipLog if nextS > sLimit { goto emitRemainder } + candidate = int(table[nextHash&tableMask]) table[nextHash&tableMask] = uint16(s) - nextHash = hash(load32(src, nextS)) - if s-candidate <= maxMatchOffset && load32(src, s) == load32(src, candidate) { + n := load6432(src, int32(nextS)) + curVal := load32(src, s) + nextVal := uint32(n) + nextHash = hash(nextVal) + if s-candidate <= maxMatchOffset && curVal == load32(src, candidate) { break } + const skipEvery = 2 + const skipBits = skipEvery * 8 + s = nextS + curVal = nextVal + nextS += skipEvery + nextVal = uint32(n >> skipBits) + candidate = int(table[nextHash&tableMask]) + table[nextHash&tableMask] = uint16(s) + nextHash = hash(nextVal) + if s-candidate <= maxMatchOffset && curVal == load32(src, candidate) { + break + } + + if skipBits*2 <= 32 { + s = nextS + curVal = nextVal + nextS += skipEvery + nextVal = uint32(n >> (skipBits * 2)) + candidate = int(table[nextHash&tableMask]) + table[nextHash&tableMask] = uint16(s) + nextHash = hash(nextVal) + if s-candidate <= maxMatchOffset && curVal == load32(src, candidate) { + break + } + } + + if skipBits*4 <= 32 { + s = nextS + curVal = nextVal + nextS += skipEvery + nextVal = uint32(n >> (skipBits * 3)) + candidate = int(table[nextHash&tableMask]) + table[nextHash&tableMask] = uint16(s) + nextHash = hash(nextVal) + if s-candidate <= maxMatchOffset && curVal == load32(src, candidate) { + break + } + + s = nextS + curVal = nextVal + nextS += skipEvery + nextVal = uint32(n >> (skipBits * 4)) + candidate = int(table[nextHash&tableMask]) + table[nextHash&tableMask] = uint16(s) + nextHash = hash(nextVal) + if s-candidate <= maxMatchOffset && curVal == load32(src, candidate) { + break + } + } } // A 4-byte match has been found. We'll later see if more than 4 bytes // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit // them as literal bytes. - emitLiteral(dst, src[nextEmit:s]) // Call emitCopy, and then see if another emitCopy could be our next // move. Repeat until we find no match for the input immediately after @@ -178,29 +228,54 @@ func (e *snappyL1) Encode(dst *tokens, src []byte) { } } s += l + // Match backwards + for base > nextEmit && candidate > 0 && s-base < maxMatchLength && src[candidate-1] == src[base-1] { + candidate-- + base-- + } + + if nextEmit < base { + emitLiteral(dst, src[nextEmit:base]) + } // matchToken is flate's equivalent of Snappy's emitCopy. dst.tokens[dst.n] = matchToken(uint32(s-base-baseMatchLength), uint32(base-candidate-baseMatchOffset)) dst.n++ nextEmit = s + if s >= sLimit { goto emitRemainder } + // Store every second hash in-between, but offset by 1. + for i := base; i < s-7; i += 6 { + x := load6432(src, int32(i)) + nextHash := hash(uint32(x)) + table[nextHash&tableMask] = uint16(i) + //nextHash = hash(uint32(x >> 8)) + //table[nextHash&tableMask] = uint16(i + 1) + //nextHash = hash(uint32(x >> 16)) + //table[nextHash&tableMask] = uint16(i + 2) + nextHash = hash(uint32(x >> 24)) + table[nextHash&tableMask] = uint16(i + 3) + //nextHash = hash(uint32(x >> 32)) + //table[nextHash&tableMask] = uint16(i + 4) + } + // We could immediately start working at s now, but to improve // compression we first update the hash table at s-1 and at s. If // another emitCopy is not our next move, also calculate nextHash // at s+1. At least on GOARCH=amd64, these three hash calculations // are faster as one load64 call (with some shifts) instead of // three load32 calls. - x := load64(src, s-1) + x := load64(src, s-3) prevHash := hash(uint32(x >> 0)) - table[prevHash&tableMask] = uint16(s - 1) - currHash := hash(uint32(x >> 8)) + currHash := hash(uint32(x >> 24)) + table[prevHash&tableMask] = uint16(s - 3) candidate = int(table[currHash&tableMask]) table[currHash&tableMask] = uint16(s) - if s-candidate > maxMatchOffset || uint32(x>>8) != load32(src, candidate) { - nextHash = hash(uint32(x >> 16)) + if s-candidate > maxMatchOffset || uint32(x>>24) != load32(src, candidate) { + nextHash = hash(uint32(x >> 32)) s++ break } @@ -249,7 +324,7 @@ type snappyL2 struct { // of matching across blocks giving better compression at a small slowdown. func (e *snappyL2) Encode(dst *tokens, src []byte) { const ( - inputMargin = 8 - 1 + inputMargin = 12 - 1 minNonLiteralBlockSize = 1 + 1 + inputMargin ) @@ -299,36 +374,46 @@ func (e *snappyL2) Encode(dst *tokens, src []byte) { // The "skip" variable keeps track of how many bytes there are since // the last match; dividing it by 32 (ie. right-shifting by five) gives // the number of bytes to move ahead for each iteration. - skip := int32(32) + const skipLog = 5 + const doEvery = 2 nextS := s var candidate tableEntry for { s = nextS - bytesBetweenHashLookups := skip >> 5 - nextS = s + bytesBetweenHashLookups - skip += bytesBetweenHashLookups + nextS = s + doEvery + (s-nextEmit)>>skipLog if nextS > sLimit { goto emitRemainder } candidate = e.table[nextHash&tableMask] - now := load3232(src, nextS) + now := load6432(src, nextS) e.table[nextHash&tableMask] = tableEntry{offset: s + e.cur, val: cv} - nextHash = hash(now) + nextHash = hash(uint32(now)) offset := s - (candidate.offset - e.cur) - if offset > maxMatchOffset || cv != candidate.val { - // Out of range or not matched. - cv = now - continue + if offset < maxMatchOffset && cv == candidate.val { + break } - break + + // Do one right away... + cv = uint32(now) + s = nextS + nextS++ + candidate = e.table[nextHash&tableMask] + now >>= 8 + e.table[nextHash&tableMask] = tableEntry{offset: s + e.cur, val: cv} + nextHash = hash(uint32(now)) + + offset = s - (candidate.offset - e.cur) + if offset < maxMatchOffset && cv == candidate.val { + break + } + cv = uint32(now) } // A 4-byte match has been found. We'll later see if more than 4 bytes // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit // them as literal bytes. - emitLiteral(dst, src[nextEmit:s]) // Call emitCopy, and then see if another emitCopy could be our next // move. Repeat until we find no match for the input immediately after @@ -343,15 +428,34 @@ func (e *snappyL2) Encode(dst *tokens, src []byte) { // literal bytes prior to s. // Extend the 4-byte match as long as possible. - // - s += 4 - t := candidate.offset - e.cur + 4 - l := e.matchlen(s, t, src) + t := candidate.offset - e.cur + l := e.matchlen(s+4, t+4, src) + + // Extend backwards + for t > 0 && nextEmit < s && src[t-1] == src[s-1] && l < maxMatchLength-4 { + s-- + t-- + l++ + } + for t <= 0 && nextEmit < s && s > 0 { + off := int32(len(e.prev)) + t - 1 + if off > 0 && e.prev[off] == src[s-1] && l < maxMatchLength-4 { + s-- + t-- + l++ + continue + } + break + } + + if nextEmit < s { + emitLiteral(dst, src[nextEmit:s]) + } // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) dst.tokens[dst.n] = matchToken(uint32(l+4-baseMatchLength), uint32(s-t-baseMatchOffset)) dst.n++ - s += l + s = s + l + 4 nextEmit = s if s >= sLimit { t += l @@ -363,16 +467,31 @@ func (e *snappyL2) Encode(dst *tokens, src []byte) { goto emitRemainder } + // Store every second hash in-between, but offset by 1. + for i := s - l - 2; i < s-7; i += 7 { + x := load6432(src, int32(i)) + nextHash := hash(uint32(x)) + e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i, val: uint32(x)} + // Skip one + x >>= 16 + nextHash = hash(uint32(x)) + e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i + 2, val: uint32(x)} + // Skip one + x >>= 16 + nextHash = hash(uint32(x)) + e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i + 4, val: uint32(x)} + } + // We could immediately start working at s now, but to improve // compression we first update the hash table at s-1 and at s. If // another emitCopy is not our next move, also calculate nextHash // at s+1. At least on GOARCH=amd64, these three hash calculations // are faster as one load64 call (with some shifts) instead of // three load32 calls. - x := load6432(src, s-1) + x := load6432(src, s-2) prevHash := hash(uint32(x)) - e.table[prevHash&tableMask] = tableEntry{offset: e.cur + s - 1, val: uint32(x)} - x >>= 8 + e.table[prevHash&tableMask] = tableEntry{offset: e.cur + s - 2, val: uint32(x)} + x >>= 16 currHash := hash(uint32(x)) candidate = e.table[currHash&tableMask] e.table[currHash&tableMask] = tableEntry{offset: e.cur + s, val: uint32(x)} From 844f232a3bcc0eb54b0dfbbb3f06ad11e6cf7636 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Wed, 22 May 2019 20:59:10 +0200 Subject: [PATCH 02/54] Skip hash for the last part of blocks. --- flate/snappy.go | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/flate/snappy.go b/flate/snappy.go index 0fbf05dec3..bf25a1d20b 100644 --- a/flate/snappy.go +++ b/flate/snappy.go @@ -247,19 +247,21 @@ func (e *snappyL1) Encode(dst *tokens, src []byte) { goto emitRemainder } - // Store every second hash in-between, but offset by 1. - for i := base; i < s-7; i += 6 { - x := load6432(src, int32(i)) - nextHash := hash(uint32(x)) - table[nextHash&tableMask] = uint16(i) - //nextHash = hash(uint32(x >> 8)) - //table[nextHash&tableMask] = uint16(i + 1) - //nextHash = hash(uint32(x >> 16)) - //table[nextHash&tableMask] = uint16(i + 2) - nextHash = hash(uint32(x >> 24)) - table[nextHash&tableMask] = uint16(i + 3) - //nextHash = hash(uint32(x >> 32)) - //table[nextHash&tableMask] = uint16(i + 4) + // Store sparse hashes inbetween, but don't bother for the last KB. + if s < len(src)-1024 { + for i := base; i < s-7; i += 6 { + x := load6432(src, int32(i)) + nextHash := hash(uint32(x)) + table[nextHash&tableMask] = uint16(i) + //nextHash = hash(uint32(x >> 8)) + //table[nextHash&tableMask] = uint16(i + 1) + //nextHash = hash(uint32(x >> 16)) + //table[nextHash&tableMask] = uint16(i + 2) + nextHash = hash(uint32(x >> 24)) + table[nextHash&tableMask] = uint16(i + 3) + //nextHash = hash(uint32(x >> 32)) + //table[nextHash&tableMask] = uint16(i + 4) + } } // We could immediately start working at s now, but to improve @@ -270,6 +272,7 @@ func (e *snappyL1) Encode(dst *tokens, src []byte) { // three load32 calls. x := load64(src, s-3) prevHash := hash(uint32(x >> 0)) + // Skip 2 bytes currHash := hash(uint32(x >> 24)) table[prevHash&tableMask] = uint16(s - 3) candidate = int(table[currHash&tableMask]) @@ -491,6 +494,7 @@ func (e *snappyL2) Encode(dst *tokens, src []byte) { x := load6432(src, s-2) prevHash := hash(uint32(x)) e.table[prevHash&tableMask] = tableEntry{offset: e.cur + s - 2, val: uint32(x)} + // Skip 1 byte. x >>= 16 currHash := hash(uint32(x)) candidate = e.table[currHash&tableMask] From 0e829b6805f718ca60328fc693d83b9aa2e4a6b7 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Thu, 23 May 2019 23:00:01 +0200 Subject: [PATCH 03/54] Tweak further. --- flate/snappy.go | 63 ++++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/flate/snappy.go b/flate/snappy.go index bf25a1d20b..e94a7a15ca 100644 --- a/flate/snappy.go +++ b/flate/snappy.go @@ -120,7 +120,7 @@ func (e *snappyL1) Encode(dst *tokens, src []byte) { // The "skip" variable keeps track of how many bytes there are since // the last match; dividing it by 32 (ie. right-shifting by five) gives // the number of bytes to move ahead for each iteration. - const skipLog = 5 + const skipLog = 4 const baseSkip = 1 nextS := s candidate := 0 @@ -140,7 +140,7 @@ func (e *snappyL1) Encode(dst *tokens, src []byte) { if s-candidate <= maxMatchOffset && curVal == load32(src, candidate) { break } - const skipEvery = 2 + const skipEvery = 1 const skipBits = skipEvery * 8 s = nextS curVal = nextVal @@ -248,7 +248,8 @@ func (e *snappyL1) Encode(dst *tokens, src []byte) { } // Store sparse hashes inbetween, but don't bother for the last KB. - if s < len(src)-1024 { + if false && s < len(src)-1024 { + // Couldn't find a combination that gave a reasonable gain. for i := base; i < s-7; i += 6 { x := load6432(src, int32(i)) nextHash := hash(uint32(x)) @@ -270,15 +271,14 @@ func (e *snappyL1) Encode(dst *tokens, src []byte) { // at s+1. At least on GOARCH=amd64, these three hash calculations // are faster as one load64 call (with some shifts) instead of // three load32 calls. - x := load64(src, s-3) - prevHash := hash(uint32(x >> 0)) - // Skip 2 bytes - currHash := hash(uint32(x >> 24)) - table[prevHash&tableMask] = uint16(s - 3) + x := load64(src, s-1) + currHash := hash(uint32(x >> 8)) + prevHash := hash(uint32(x)) candidate = int(table[currHash&tableMask]) + table[prevHash&tableMask] = uint16(s - 1) table[currHash&tableMask] = uint16(s) - if s-candidate > maxMatchOffset || uint32(x>>24) != load32(src, candidate) { - nextHash = hash(uint32(x >> 32)) + if s-candidate > maxMatchOffset || uint32(x>>8) != load32(src, candidate) { + nextHash = hash(uint32(x >> 16)) s++ break } @@ -377,7 +377,7 @@ func (e *snappyL2) Encode(dst *tokens, src []byte) { // The "skip" variable keeps track of how many bytes there are since // the last match; dividing it by 32 (ie. right-shifting by five) gives // the number of bytes to move ahead for each iteration. - const skipLog = 5 + const skipLog = 4 const doEvery = 2 nextS := s @@ -471,18 +471,20 @@ func (e *snappyL2) Encode(dst *tokens, src []byte) { } // Store every second hash in-between, but offset by 1. - for i := s - l - 2; i < s-7; i += 7 { - x := load6432(src, int32(i)) - nextHash := hash(uint32(x)) - e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i, val: uint32(x)} - // Skip one - x >>= 16 - nextHash = hash(uint32(x)) - e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i + 2, val: uint32(x)} - // Skip one - x >>= 16 - nextHash = hash(uint32(x)) - e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i + 4, val: uint32(x)} + if false { + for i := s - l - 2; i < s-7; i += 7 { + x := load6432(src, int32(i)) + nextHash := hash(uint32(x)) + e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i, val: uint32(x)} + // Skip one + x >>= 16 + nextHash = hash(uint32(x)) + e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i + 2, val: uint32(x)} + // Skip one + x >>= 16 + nextHash = hash(uint32(x)) + e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i + 4, val: uint32(x)} + } } // We could immediately start working at s now, but to improve @@ -492,17 +494,18 @@ func (e *snappyL2) Encode(dst *tokens, src []byte) { // are faster as one load64 call (with some shifts) instead of // three load32 calls. x := load6432(src, s-2) + o := e.cur + s - 2 prevHash := hash(uint32(x)) - e.table[prevHash&tableMask] = tableEntry{offset: e.cur + s - 2, val: uint32(x)} - // Skip 1 byte. - x >>= 16 - currHash := hash(uint32(x)) + prevHash2 := hash(uint32(x >> 8)) + e.table[prevHash&tableMask] = tableEntry{offset: o, val: uint32(x)} + e.table[prevHash2&tableMask] = tableEntry{offset: o + 1, val: uint32(x >> 8)} + currHash := hash(uint32(x >> 16)) candidate = e.table[currHash&tableMask] - e.table[currHash&tableMask] = tableEntry{offset: e.cur + s, val: uint32(x)} + e.table[currHash&tableMask] = tableEntry{offset: o + 2, val: uint32(x >> 16)} offset := s - (candidate.offset - e.cur) - if offset > maxMatchOffset || uint32(x) != candidate.val { - cv = uint32(x >> 8) + if offset > maxMatchOffset || uint32(x>>16) != candidate.val { + cv = uint32(x >> 24) nextHash = hash(cv) s++ break From 7728ed274cb7a5efd740ade9f8bfbaf21d816f16 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Thu, 23 May 2019 23:49:15 +0200 Subject: [PATCH 04/54] Optimize deflate bit writing. Mostly avoiding bounds checks. --- flate/huffman_bit_writer.go | 40 +++++++++++++++++++++++++++++++------ flate/token.go | 27 +++++++++---------------- 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/flate/huffman_bit_writer.go b/flate/huffman_bit_writer.go index f9b2a699a3..92b74a2db6 100644 --- a/flate/huffman_bit_writer.go +++ b/flate/huffman_bit_writer.go @@ -35,7 +35,7 @@ const ( ) // The number of extra bits needed by length code X - LENGTH_CODES_START. -var lengthExtraBits = []int8{ +var lengthExtraBits = [32]int8{ /* 257 */ 0, 0, 0, /* 260 */ 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, /* 270 */ 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, @@ -43,7 +43,7 @@ var lengthExtraBits = []int8{ } // The length indicated by length code X - LENGTH_CODES_START. -var lengthBase = []uint32{ +var lengthBase = []uint8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 255, @@ -548,9 +548,21 @@ func (w *huffmanBitWriter) indexTokens(tokens []token) (numLiterals, numOffsets w.offsetFreq[i] = 0 } + if len(tokens) == 0 { + return + } + + // Only last token should be endBlockMarker. + if tokens[len(tokens)-1] == endBlockMarker { + w.literalFreq[endBlockMarker]++ + tokens = tokens[:len(tokens)-1] + } + + // Pure literals + lits := w.literalFreq[:256] for _, t := range tokens { - if t < matchType { - w.literalFreq[t.literal()]++ + if t < endBlockMarker { + lits[t.literal()]++ continue } length := t.length() @@ -586,16 +598,29 @@ func (w *huffmanBitWriter) writeTokens(tokens []token, leCodes, oeCodes []hcode) if w.err != nil { return } + if len(tokens) == 0 { + return + } + + // Only last token should be endBlockMarker. + var deferEOB bool + if tokens[len(tokens)-1] == endBlockMarker { + tokens = tokens[:len(tokens)-1] + deferEOB = true + } + + lits := leCodes[:256] for _, t := range tokens { if t < matchType { - w.writeCode(leCodes[t.literal()]) + w.writeCode(lits[t.literal()]) continue } + // Write the length length := t.length() lengthCode := lengthCode(length) w.writeCode(leCodes[lengthCode+lengthCodesStart]) - extraLengthBits := uint(lengthExtraBits[lengthCode]) + extraLengthBits := uint(lengthExtraBits[lengthCode&31]) if extraLengthBits > 0 { extraLength := int32(length - lengthBase[lengthCode]) w.writeBits(extraLength, extraLengthBits) @@ -610,6 +635,9 @@ func (w *huffmanBitWriter) writeTokens(tokens []token, leCodes, oeCodes []hcode) w.writeBits(extraOffset, extraOffsetBits) } } + if deferEOB { + w.writeCode(leCodes[endBlockMarker]) + } } // huffOffset is a static offset encoder used for huffman only encoding. diff --git a/flate/token.go b/flate/token.go index 4f275ea61d..4922e3f37f 100644 --- a/flate/token.go +++ b/flate/token.go @@ -4,8 +4,6 @@ package flate -import "fmt" - const ( // 2 bits: type 0 = literal 1=EOF 2=Match 3=Unused // 8 bits: xlength = length - MIN_MATCH_LENGTH @@ -19,7 +17,7 @@ const ( // The length code for length X (MIN_MATCH_LENGTH <= X <= MAX_MATCH_LENGTH) // is lengthCodes[length - MIN_MATCH_LENGTH] -var lengthCodes = [...]uint32{ +var lengthCodes = [256]uint8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, @@ -48,7 +46,7 @@ var lengthCodes = [...]uint32{ 27, 27, 27, 27, 27, 28, } -var offsetCodes = [...]uint32{ +var offsetCodes = [256]uint32{ 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, @@ -82,34 +80,27 @@ func matchToken(xlength uint32, xoffset uint32) token { return token(matchType + xlength< maxMatchLength || xoffset > maxMatchOffset { - panic(fmt.Sprintf("Invalid match: len: %d, offset: %d\n", xlength, xoffset)) - return token(matchType) - } - return token(matchType + xlength<> lengthShift) } +func (t token) length() uint8 { return uint8(t >> lengthShift) } -func lengthCode(len uint32) uint32 { return lengthCodes[len] } +// The code is never more than 8 bits, but is returned as uint8 for convenience. +func lengthCode(len uint8) uint32 { return uint32(lengthCodes[len]) } // Returns the offset code corresponding to a specific offset func offsetCode(off uint32) uint32 { if off < uint32(len(offsetCodes)) { - return offsetCodes[off] + return offsetCodes[off&255] } else if off>>7 < uint32(len(offsetCodes)) { - return offsetCodes[off>>7] + 14 + return offsetCodes[(off>>7)&255] + 14 } else { - return offsetCodes[off>>14] + 28 + return offsetCodes[(off>>14)&255] + 28 } } From f1182b12b7a904285390b3e330ff1052b9361238 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Fri, 24 May 2019 00:04:13 +0200 Subject: [PATCH 05/54] Avoid a lot of bounds checks for byte buffer. --- flate/huffman_bit_writer.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/flate/huffman_bit_writer.go b/flate/huffman_bit_writer.go index 92b74a2db6..1ba5dc2dfa 100644 --- a/flate/huffman_bit_writer.go +++ b/flate/huffman_bit_writer.go @@ -86,9 +86,9 @@ type huffmanBitWriter struct { // and then the low nbits of bits. bits uint64 nbits uint - bytes [bufferSize]byte + bytes [256]byte codegenFreq [codegenCodeCount]int32 - nbytes int + nbytes uint8 literalFreq []int32 offsetFreq []int32 codegen []uint8 @@ -113,7 +113,7 @@ func newHuffmanBitWriter(w io.Writer) *huffmanBitWriter { func (w *huffmanBitWriter) reset(writer io.Writer) { w.writer = writer w.bits, w.nbits, w.nbytes, w.err = 0, 0, 0, nil - w.bytes = [bufferSize]byte{} + w.bytes = [256]byte{} } func (w *huffmanBitWriter) flush() { @@ -333,9 +333,6 @@ func (w *huffmanBitWriter) storedSize(in []byte) (int, bool) { } func (w *huffmanBitWriter) writeCode(c hcode) { - if w.err != nil { - return - } w.bits |= uint64(c.code) << w.nbits w.nbits += uint(c.len) if w.nbits >= 48 { @@ -352,6 +349,10 @@ func (w *huffmanBitWriter) writeCode(c hcode) { bytes[5] = byte(bits >> 40) n += 6 if n >= bufferFlushSize { + if w.err != nil { + n = 0 + return + } w.write(w.bytes[:n]) n = 0 } From df7a94e7f7bb8ee172028740149a0785dc4de89a Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Fri, 24 May 2019 00:06:36 +0200 Subject: [PATCH 06/54] Don't check error value unless there are sideeffects. --- flate/huffman_bit_writer.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/flate/huffman_bit_writer.go b/flate/huffman_bit_writer.go index 1ba5dc2dfa..614ee0c901 100644 --- a/flate/huffman_bit_writer.go +++ b/flate/huffman_bit_writer.go @@ -145,9 +145,6 @@ func (w *huffmanBitWriter) write(b []byte) { } func (w *huffmanBitWriter) writeBits(b int32, nb uint) { - if w.err != nil { - return - } w.bits |= uint64(b) << w.nbits w.nbits += nb if w.nbits >= 48 { @@ -164,6 +161,10 @@ func (w *huffmanBitWriter) writeBits(b int32, nb uint) { bytes[5] = byte(bits >> 40) n += 6 if n >= bufferFlushSize { + if w.err != nil { + n = 0 + return + } w.write(w.bytes[:n]) n = 0 } From 06a10a9160d194957d5b24590138de2a45c0349e Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Fri, 24 May 2019 00:44:38 +0200 Subject: [PATCH 07/54] Avoid more bounds checks. --- flate/huffman_bit_writer.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/flate/huffman_bit_writer.go b/flate/huffman_bit_writer.go index 614ee0c901..fea477e06e 100644 --- a/flate/huffman_bit_writer.go +++ b/flate/huffman_bit_writer.go @@ -43,14 +43,14 @@ var lengthExtraBits = [32]int8{ } // The length indicated by length code X - LENGTH_CODES_START. -var lengthBase = []uint8{ +var lengthBase = [32]uint8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 255, } // offset code word extra bits. -var offsetExtraBits = []int8{ +var offsetExtraBits = [64]int8{ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, @@ -58,7 +58,7 @@ var offsetExtraBits = []int8{ 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, } -var offsetBase = []uint32{ +var offsetBase = [64]uint32{ /* normal deflate */ 0x000000, 0x000001, 0x000002, 0x000003, 0x000004, 0x000006, 0x000008, 0x00000c, 0x000010, 0x000018, @@ -462,7 +462,7 @@ func (w *huffmanBitWriter) writeBlock(tokens []token, eof bool, input []byte) { } for offsetCode := 4; offsetCode < numOffsets; offsetCode++ { // First four offset codes have extra size = 0. - extraBits += int(w.offsetFreq[offsetCode]) * int(offsetExtraBits[offsetCode]) + extraBits += int(w.offsetFreq[offsetCode]) * int(offsetExtraBits[offsetCode&63]) } } @@ -624,16 +624,16 @@ func (w *huffmanBitWriter) writeTokens(tokens []token, leCodes, oeCodes []hcode) w.writeCode(leCodes[lengthCode+lengthCodesStart]) extraLengthBits := uint(lengthExtraBits[lengthCode&31]) if extraLengthBits > 0 { - extraLength := int32(length - lengthBase[lengthCode]) + extraLength := int32(length - lengthBase[lengthCode&31]) w.writeBits(extraLength, extraLengthBits) } // Write the offset offset := t.offset() offsetCode := offsetCode(offset) w.writeCode(oeCodes[offsetCode]) - extraOffsetBits := uint(offsetExtraBits[offsetCode]) + extraOffsetBits := uint(offsetExtraBits[offsetCode&63]) if extraOffsetBits > 0 { - extraOffset := int32(offset - offsetBase[offsetCode]) + extraOffset := int32(offset - offsetBase[offsetCode&63]) w.writeBits(extraOffset, extraOffsetBits) } } From c9f0cde9de9b499ebb784709da9d631b08330ba1 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Fri, 24 May 2019 17:14:17 +0200 Subject: [PATCH 08/54] Remove more bounds checks --- flate/copy.go | 32 ---------------------- flate/copy_test.go | 54 ------------------------------------- flate/huffman_bit_writer.go | 39 +++++++++++++-------------- flate/token.go | 2 +- flate/writer_test.go | 4 +-- 5 files changed, 21 insertions(+), 110 deletions(-) delete mode 100644 flate/copy.go delete mode 100644 flate/copy_test.go diff --git a/flate/copy.go b/flate/copy.go deleted file mode 100644 index a3200a8f49..0000000000 --- a/flate/copy.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package flate - -// forwardCopy is like the built-in copy function except that it always goes -// forward from the start, even if the dst and src overlap. -// It is equivalent to: -// for i := 0; i < n; i++ { -// mem[dst+i] = mem[src+i] -// } -func forwardCopy(mem []byte, dst, src, n int) { - if dst <= src { - copy(mem[dst:dst+n], mem[src:src+n]) - return - } - for { - if dst >= src+n { - copy(mem[dst:dst+n], mem[src:src+n]) - return - } - // There is some forward overlap. The destination - // will be filled with a repeated pattern of mem[src:src+k]. - // We copy one instance of the pattern here, then repeat. - // Each time around this loop k will double. - k := dst - src - copy(mem[dst:dst+k], mem[src:src+k]) - n -= k - dst += k - } -} diff --git a/flate/copy_test.go b/flate/copy_test.go deleted file mode 100644 index 2011b1547c..0000000000 --- a/flate/copy_test.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package flate - -import ( - "testing" -) - -func TestForwardCopy(t *testing.T) { - testCases := []struct { - dst0, dst1 int - src0, src1 int - want string - }{ - {0, 9, 0, 9, "012345678"}, - {0, 5, 4, 9, "45678"}, - {4, 9, 0, 5, "01230"}, - {1, 6, 3, 8, "34567"}, - {3, 8, 1, 6, "12121"}, - {0, 9, 3, 6, "345"}, - {3, 6, 0, 9, "012"}, - {1, 6, 0, 9, "00000"}, - {0, 4, 7, 8, "7"}, - {0, 1, 6, 8, "6"}, - {4, 4, 6, 9, ""}, - {2, 8, 6, 6, ""}, - {0, 0, 0, 0, ""}, - } - for _, tc := range testCases { - b := []byte("0123456789") - n := tc.dst1 - tc.dst0 - if tc.src1-tc.src0 < n { - n = tc.src1 - tc.src0 - } - forwardCopy(b, tc.dst0, tc.src0, n) - got := string(b[tc.dst0 : tc.dst0+n]) - if got != tc.want { - t.Errorf("dst=b[%d:%d], src=b[%d:%d]: got %q, want %q", - tc.dst0, tc.dst1, tc.src0, tc.src1, got, tc.want) - } - // Check that the bytes outside of dst[:n] were not modified. - for i, x := range b { - if i >= tc.dst0 && i < tc.dst0+n { - continue - } - if int(x) != '0'+i { - t.Errorf("dst=b[%d:%d], src=b[%d:%d]: copy overrun at b[%d]: got '%c', want '%c'", - tc.dst0, tc.dst1, tc.src0, tc.src1, i, x, '0'+i) - } - } - } -} diff --git a/flate/huffman_bit_writer.go b/flate/huffman_bit_writer.go index fea477e06e..1123677070 100644 --- a/flate/huffman_bit_writer.go +++ b/flate/huffman_bit_writer.go @@ -152,13 +152,12 @@ func (w *huffmanBitWriter) writeBits(b int32, nb uint) { w.bits >>= 48 w.nbits -= 48 n := w.nbytes - bytes := w.bytes[n : n+6] - bytes[0] = byte(bits) - bytes[1] = byte(bits >> 8) - bytes[2] = byte(bits >> 16) - bytes[3] = byte(bits >> 24) - bytes[4] = byte(bits >> 32) - bytes[5] = byte(bits >> 40) + w.bytes[n] = byte(bits) + w.bytes[n+1] = byte(bits >> 8) + w.bytes[n+2] = byte(bits >> 16) + w.bytes[n+3] = byte(bits >> 24) + w.bytes[n+4] = byte(bits >> 32) + w.bytes[n+5] = byte(bits >> 40) n += 6 if n >= bufferFlushSize { if w.err != nil { @@ -341,13 +340,12 @@ func (w *huffmanBitWriter) writeCode(c hcode) { w.bits >>= 48 w.nbits -= 48 n := w.nbytes - bytes := w.bytes[n : n+6] - bytes[0] = byte(bits) - bytes[1] = byte(bits >> 8) - bytes[2] = byte(bits >> 16) - bytes[3] = byte(bits >> 24) - bytes[4] = byte(bits >> 32) - bytes[5] = byte(bits >> 40) + w.bytes[n] = byte(bits) + w.bytes[n+1] = byte(bits >> 8) + w.bytes[n+2] = byte(bits >> 16) + w.bytes[n+3] = byte(bits >> 24) + w.bytes[n+4] = byte(bits >> 32) + w.bytes[n+5] = byte(bits >> 40) n += 6 if n >= bufferFlushSize { if w.err != nil { @@ -709,13 +707,12 @@ func (w *huffmanBitWriter) writeBlockHuff(eof bool, input []byte) { bits := w.bits w.bits >>= 48 w.nbits -= 48 - bytes := w.bytes[n : n+6] - bytes[0] = byte(bits) - bytes[1] = byte(bits >> 8) - bytes[2] = byte(bits >> 16) - bytes[3] = byte(bits >> 24) - bytes[4] = byte(bits >> 32) - bytes[5] = byte(bits >> 40) + w.bytes[n] = byte(bits) + w.bytes[n+1] = byte(bits >> 8) + w.bytes[n+2] = byte(bits >> 16) + w.bytes[n+3] = byte(bits >> 24) + w.bytes[n+4] = byte(bits >> 32) + w.bytes[n+5] = byte(bits >> 40) n += 6 if n < bufferFlushSize { continue diff --git a/flate/token.go b/flate/token.go index 4922e3f37f..141299b973 100644 --- a/flate/token.go +++ b/flate/token.go @@ -91,7 +91,7 @@ func (t token) offset() uint32 { return uint32(t) & offsetMask } func (t token) length() uint8 { return uint8(t >> lengthShift) } -// The code is never more than 8 bits, but is returned as uint8 for convenience. +// The code is never more than 8 bits, but is returned as uint32 for convenience. func lengthCode(len uint8) uint32 { return uint32(lengthCodes[len]) } // Returns the offset code corresponding to a specific offset diff --git a/flate/writer_test.go b/flate/writer_test.go index 024512afb2..e02b546cd3 100644 --- a/flate/writer_test.go +++ b/flate/writer_test.go @@ -15,7 +15,6 @@ import ( ) func benchmarkEncoder(b *testing.B, testfile, level, n int) { - b.StopTimer() b.SetBytes(int64(n)) buf0, err := ioutil.ReadFile(testfiles[testfile]) if err != nil { @@ -34,7 +33,8 @@ func benchmarkEncoder(b *testing.B, testfile, level, n int) { buf0 = nil runtime.GC() w, err := NewWriter(ioutil.Discard, level) - b.StartTimer() + b.ResetTimer() + b.ReportAllocs() for i := 0; i < b.N; i++ { w.Reset(ioutil.Discard) _, err = w.Write(buf1) From 1e6f5a8ecf613ab6dc985d4cf615c4a56c7437ff Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Fri, 24 May 2019 17:47:03 +0200 Subject: [PATCH 09/54] Remove final bounds checks from indexer+writer. --- flate/huffman_bit_writer.go | 31 +++++++++++++++++++------------ flate/huffman_code.go | 5 ++++- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/flate/huffman_bit_writer.go b/flate/huffman_bit_writer.go index 1123677070..4f3a4a76c0 100644 --- a/flate/huffman_bit_writer.go +++ b/flate/huffman_bit_writer.go @@ -101,8 +101,8 @@ type huffmanBitWriter struct { func newHuffmanBitWriter(w io.Writer) *huffmanBitWriter { return &huffmanBitWriter{ writer: w, - literalFreq: make([]int32, maxNumLit), - offsetFreq: make([]int32, offsetCodeCount), + literalFreq: make([]int32, lengthCodesStart+32), + offsetFreq: make([]int32, 32), codegen: make([]uint8, maxNumLit+offsetCodeCount+1), literalEncoding: newHuffmanEncoder(maxNumLit), codegenEncoding: newHuffmanEncoder(codegenCodeCount), @@ -558,8 +558,11 @@ func (w *huffmanBitWriter) indexTokens(tokens []token) (numLiterals, numOffsets tokens = tokens[:len(tokens)-1] } - // Pure literals + // Create slices up to the next power of two to avoid bounds checks. lits := w.literalFreq[:256] + offs := w.offsetFreq[:32] + lengths := w.literalFreq[lengthCodesStart:] + lengths = lengths[:32] for _, t := range tokens { if t < endBlockMarker { lits[t.literal()]++ @@ -567,8 +570,8 @@ func (w *huffmanBitWriter) indexTokens(tokens []token) (numLiterals, numOffsets } length := t.length() offset := t.offset() - w.literalFreq[lengthCodesStart+lengthCode(length)]++ - w.offsetFreq[offsetCode(offset)]++ + lengths[lengthCode(length)&31]++ + offs[offsetCode(offset)&31]++ } // get the number of literals @@ -587,8 +590,8 @@ func (w *huffmanBitWriter) indexTokens(tokens []token) (numLiterals, numOffsets w.offsetFreq[0] = 1 numOffsets = 1 } - w.literalEncoding.generate(w.literalFreq, 15) - w.offsetEncoding.generate(w.offsetFreq, 15) + w.literalEncoding.generate(w.literalFreq[:maxNumLit], 15) + w.offsetEncoding.generate(w.offsetFreq[:offsetCodeCount], 15) return } @@ -608,8 +611,12 @@ func (w *huffmanBitWriter) writeTokens(tokens []token, leCodes, oeCodes []hcode) tokens = tokens[:len(tokens)-1] deferEOB = true } - + + // Create slices up to the next power of two to avoid bounds checks. lits := leCodes[:256] + offs := oeCodes[:32] + lengths := leCodes[lengthCodesStart:] + lengths = lengths[:32] for _, t := range tokens { if t < matchType { w.writeCode(lits[t.literal()]) @@ -619,7 +626,7 @@ func (w *huffmanBitWriter) writeTokens(tokens []token, leCodes, oeCodes []hcode) // Write the length length := t.length() lengthCode := lengthCode(length) - w.writeCode(leCodes[lengthCode+lengthCodesStart]) + w.writeCode(lengths[lengthCode&31]) extraLengthBits := uint(lengthExtraBits[lengthCode&31]) if extraLengthBits > 0 { extraLength := int32(length - lengthBase[lengthCode&31]) @@ -628,7 +635,7 @@ func (w *huffmanBitWriter) writeTokens(tokens []token, leCodes, oeCodes []hcode) // Write the offset offset := t.offset() offsetCode := offsetCode(offset) - w.writeCode(oeCodes[offsetCode]) + w.writeCode(offs[offsetCode&31]) extraOffsetBits := uint(offsetExtraBits[offsetCode&63]) if extraOffsetBits > 0 { extraOffset := int32(offset - offsetBase[offsetCode&63]) @@ -648,7 +655,7 @@ func init() { w := newHuffmanBitWriter(nil) w.offsetFreq[0] = 1 huffOffset = newHuffmanEncoder(offsetCodeCount) - huffOffset.generate(w.offsetFreq, 15) + huffOffset.generate(w.offsetFreq[:offsetCodeCount], 15) } // writeBlockHuff encodes a block of bytes as either @@ -672,7 +679,7 @@ func (w *huffmanBitWriter) writeBlockHuff(eof bool, input []byte) { const numLiterals = endBlockMarker + 1 const numOffsets = 1 - w.literalEncoding.generate(w.literalFreq, 15) + w.literalEncoding.generate(w.literalFreq[:maxNumLit], 15) // Figure out smallest code. // Always use dynamic Huffman or Store diff --git a/flate/huffman_code.go b/flate/huffman_code.go index bdcbd823b0..7ce369a647 100644 --- a/flate/huffman_code.go +++ b/flate/huffman_code.go @@ -6,6 +6,7 @@ package flate import ( "math" + "math/bits" "sort" ) @@ -56,7 +57,9 @@ func (h *hcode) set(code uint16, length uint16) { func maxNode() literalNode { return literalNode{math.MaxUint16, math.MaxInt32} } func newHuffmanEncoder(size int) *huffmanEncoder { - return &huffmanEncoder{codes: make([]hcode, size)} + // Make capacity to next power of two. + c := uint(bits.Len32(uint32(size-1))) + return &huffmanEncoder{codes: make([]hcode, size, 1 << c)} } // Generates a HuffmanCode corresponding to the fixed literal table From d81e961ea8b824a82f3294628d6c26bfc23639d1 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Fri, 24 May 2019 22:28:48 +0200 Subject: [PATCH 10/54] fmt it --- flate/huffman_bit_writer.go | 2 +- flate/huffman_code.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/flate/huffman_bit_writer.go b/flate/huffman_bit_writer.go index 4f3a4a76c0..f46c654189 100644 --- a/flate/huffman_bit_writer.go +++ b/flate/huffman_bit_writer.go @@ -611,7 +611,7 @@ func (w *huffmanBitWriter) writeTokens(tokens []token, leCodes, oeCodes []hcode) tokens = tokens[:len(tokens)-1] deferEOB = true } - + // Create slices up to the next power of two to avoid bounds checks. lits := leCodes[:256] offs := oeCodes[:32] diff --git a/flate/huffman_code.go b/flate/huffman_code.go index 7ce369a647..f65f793361 100644 --- a/flate/huffman_code.go +++ b/flate/huffman_code.go @@ -58,8 +58,8 @@ func maxNode() literalNode { return literalNode{math.MaxUint16, math.MaxInt32} } func newHuffmanEncoder(size int) *huffmanEncoder { // Make capacity to next power of two. - c := uint(bits.Len32(uint32(size-1))) - return &huffmanEncoder{codes: make([]hcode, size, 1 << c)} + c := uint(bits.Len32(uint32(size - 1))) + return &huffmanEncoder{codes: make([]hcode, size, 1< Date: Mon, 27 May 2019 20:55:00 +0200 Subject: [PATCH 11/54] Check 8 bytes/loop for matches. --- flate/deflate.go | 4 +++- flate/snappy.go | 62 +++++++++++++++++++++++------------------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/flate/deflate.go b/flate/deflate.go index 9e6e7ff0cf..7f7cfdec38 100644 --- a/flate/deflate.go +++ b/flate/deflate.go @@ -278,7 +278,7 @@ func (d *compressor) findMatch(pos int, prevHead int, prevLength int, lookahead for i := prevHead; tries > 0; tries-- { if wEnd == win[i+length] { - n := matchLen(win[i:], wPos, minMatchLook) + n := matchLen(win[i:i+minMatchLook], wPos) if n > length && (n > minMatchLength || pos-i <= 4096) { length = n @@ -390,6 +390,7 @@ func bulkHash4(b []byte, dst []uint32) { } } +/* // matchLen returns the number of matching bytes in a and b // up to length 'max'. Both slices must be at least 'max' // bytes in size. @@ -403,6 +404,7 @@ func matchLen(a, b []byte, max int) int { } return max } +*/ func (d *compressor) initDeflate() { d.window = make([]byte, 2*windowSize) diff --git a/flate/snappy.go b/flate/snappy.go index e94a7a15ca..a1538270ab 100644 --- a/flate/snappy.go +++ b/flate/snappy.go @@ -5,6 +5,8 @@ package flate +import "math/bits" + // emitLiteral writes a literal chunk and returns the number of bytes written. func emitLiteral(dst *tokens, lit []byte) { ol := int(dst.n) @@ -219,14 +221,7 @@ func (e *snappyL1) Encode(dst *tokens, src []byte) { } a := src[s:s1] b := src[candidate+4:] - b = b[:len(a)] - l := len(a) - for i := range a { - if a[i] != b[i] { - l = i - break - } - } + l := matchLen(a, b) s += l // Match backwards for base > nextEmit && candidate > 0 && s-base < maxMatchLength && src[candidate-1] == src[base-1] { @@ -972,14 +967,8 @@ func (e *snappyGen) matchlen(s, t int32, src []byte) int32 { if t >= 0 { b := src[t:] a := src[s:s1] - b = b[:len(a)] // Extend the match to be as long as possible. - for i := range a { - if a[i] != b[i] { - return int32(i) - } - } - return int32(len(a)) + return int32(matchLen(a, b)) } // We found a match in the previous block. @@ -994,29 +983,17 @@ func (e *snappyGen) matchlen(s, t int32, src []byte) int32 { if len(b) > len(a) { b = b[:len(a)] } - a = a[:len(b)] - for i := range b { - if a[i] != b[i] { - return int32(i) - } - } - + n := matchLen(b, a) // If we reached our limit, we matched everything we are // allowed to in the previous block and we return. - n := int32(len(b)) - if int(s+n) == s1 { - return n + if len(b) != n || int(s)+n == s1 { + return int32(n) } // Continue looking for more matches in the current block. - a = src[s+n : s1] + a = src[int(s)+n : s1] b = src[:len(a)] - for i := range a { - if a[i] != b[i] { - return int32(i) + n - } - } - return int32(len(a)) + n + return int32(matchLen(a, b) + n) } // Reset the encoding table. @@ -1024,3 +1001,24 @@ func (e *snappyGen) Reset() { e.prev = e.prev[:0] e.cur += maxMatchOffset } + +// matchLen returns the maximum length. +// 'a' must be the shortest of the two. +func matchLen(a, b []byte) int { + b = b[:len(a)] + for i := 0; i < len(a)-7; i += 8 { + if diff := load64(a, i) ^ load64(b, i); diff != 0 { + return i + (bits.TrailingZeros64(diff) >> 3) + } + } + checked := (len(a) >> 3) << 3 + a = a[checked:] + b = b[checked:] + // TODO: We could do a 4 check. + for i := range a { + if a[i] != b[i] { + return int(i) + checked + } + } + return len(a) + checked +} From 80fae85a32572b3679a03bf57dddd6ec36636052 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Tue, 28 May 2019 09:31:46 +0200 Subject: [PATCH 12/54] Try 4 byte match before doing 8 bytes. --- flate/snappy.go | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/flate/snappy.go b/flate/snappy.go index a1538270ab..4752ee56cf 100644 --- a/flate/snappy.go +++ b/flate/snappy.go @@ -1006,15 +1006,22 @@ func (e *snappyGen) Reset() { // 'a' must be the shortest of the two. func matchLen(a, b []byte) int { b = b[:len(a)] - for i := 0; i < len(a)-7; i += 8 { - if diff := load64(a, i) ^ load64(b, i); diff != 0 { - return i + (bits.TrailingZeros64(diff) >> 3) + var checked int + if len(a) > 4 { + // Try 4 bytes first + if diff := load32(a, 0) ^ load32(b, 0); diff != 0 { + return bits.TrailingZeros32(diff) >> 3 } + // Switch to 8 byte matching. + for i := 4; i < len(a)-7; i += 8 { + if diff := load64(a, i) ^ load64(b, i); diff != 0 { + return i + (bits.TrailingZeros64(diff) >> 3) + } + } + checked = 4+((len(a)-4) >> 3) << 3 + a = a[checked:] + b = b[checked:] } - checked := (len(a) >> 3) << 3 - a = a[checked:] - b = b[checked:] - // TODO: We could do a 4 check. for i := range a { if a[i] != b[i] { return int(i) + checked From 87fe04df99ede15582a21895f3f6f530d9537ad2 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Tue, 28 May 2019 10:01:07 +0200 Subject: [PATCH 13/54] fmt --- flate/snappy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flate/snappy.go b/flate/snappy.go index 4752ee56cf..666f32188e 100644 --- a/flate/snappy.go +++ b/flate/snappy.go @@ -1018,7 +1018,7 @@ func matchLen(a, b []byte) int { return i + (bits.TrailingZeros64(diff) >> 3) } } - checked = 4+((len(a)-4) >> 3) << 3 + checked = 4 + ((len(a)-4)>>3)<<3 a = a[checked:] b = b[checked:] } From e5a97dbbc9e2e2f7d1fbaa404c2863fa8e594a2e Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Tue, 28 May 2019 19:04:55 +0200 Subject: [PATCH 14/54] Remove unused code. --- flate/deflate.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/flate/deflate.go b/flate/deflate.go index 7f7cfdec38..5c2f3e4931 100644 --- a/flate/deflate.go +++ b/flate/deflate.go @@ -390,22 +390,6 @@ func bulkHash4(b []byte, dst []uint32) { } } -/* -// matchLen returns the number of matching bytes in a and b -// up to length 'max'. Both slices must be at least 'max' -// bytes in size. -func matchLen(a, b []byte, max int) int { - a = a[:max] - b = b[:len(a)] - for i, av := range a { - if b[i] != av { - return i - } - } - return max -} -*/ - func (d *compressor) initDeflate() { d.window = make([]byte, 2*windowSize) d.hashOffset = 1 From ac656c750585b2c15177ac9854e9a687cafa7c69 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Sun, 2 Jun 2019 12:46:15 +0200 Subject: [PATCH 15/54] Simplify bounds checks --- flate/snappy.go | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/flate/snappy.go b/flate/snappy.go index 666f32188e..57a9579122 100644 --- a/flate/snappy.go +++ b/flate/snappy.go @@ -53,12 +53,31 @@ const ( ) func load32(b []byte, i int) uint32 { - b = b[i : i+4 : len(b)] // Help the compiler eliminate bounds checks on the next line. + // Help the compiler eliminate bounds checks on the read so it can be done in a single read. + b = b[i:] + b = b[:4] return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 } func load64(b []byte, i int) uint64 { - b = b[i : i+8 : len(b)] // Help the compiler eliminate bounds checks on the next line. + // Help the compiler eliminate bounds checks on the read so it can be done in a single read. + b = b[i:] + b = b[:8] + return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | + uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 +} + +func load3232(b []byte, i int32) uint32 { + // Help the compiler eliminate bounds checks on the read so it can be done in a single read. + b = b[i:] + b = b[:4] + return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 +} + +func load6432(b []byte, i int32) uint64 { + // Help the compiler eliminate bounds checks on the read so it can be done in a single read. + b = b[i:] + b = b[:8] return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 } @@ -291,17 +310,6 @@ type tableEntry struct { offset int32 } -func load3232(b []byte, i int32) uint32 { - b = b[i : i+4 : len(b)] // Help the compiler eliminate bounds checks on the next line. - return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 -} - -func load6432(b []byte, i int32) uint64 { - b = b[i : i+8 : len(b)] // Help the compiler eliminate bounds checks on the next line. - return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | - uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 -} - // snappyGen maintains the table for matches, // and the previous byte block for level 2. // This is the generic implementation. From 3d74e1789dcdc507a9bd137ad5ae9de838d0ae03 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Mon, 10 Jun 2019 16:03:49 +0200 Subject: [PATCH 16/54] Rename to fastEnc --- flate/deflate.go | 4 +- flate/{snappy.go => fast_encoder.go} | 56 ++++++++++++++-------------- 2 files changed, 30 insertions(+), 30 deletions(-) rename flate/{snappy.go => fast_encoder.go} (96%) diff --git a/flate/deflate.go b/flate/deflate.go index 5c2f3e4931..b30f3f6f0e 100644 --- a/flate/deflate.go +++ b/flate/deflate.go @@ -116,7 +116,7 @@ type compressor struct { err error ii uint16 // position of last match, intended to overflow to reset. - snap snappyEnc + snap fastEnc hashMatch [maxMatchLength + minMatchLength]uint32 } @@ -1153,7 +1153,7 @@ func (d *compressor) init(w io.Writer, level int) (err error) { d.fill = (*compressor).fillBlock d.step = (*compressor).storeHuff case level >= 1 && level <= 4: - d.snap = newSnappy(level) + d.snap = newFastEnc(level) d.window = make([]byte, maxStoreBlockSize) d.fill = (*compressor).fillBlock d.step = (*compressor).storeSnappy diff --git a/flate/snappy.go b/flate/fast_encoder.go similarity index 96% rename from flate/snappy.go rename to flate/fast_encoder.go index 57a9579122..0488f28b19 100644 --- a/flate/snappy.go +++ b/flate/fast_encoder.go @@ -22,21 +22,21 @@ func emitCopy(dst *tokens, offset, length int) { dst.n++ } -type snappyEnc interface { +type fastEnc interface { Encode(dst *tokens, src []byte) Reset() } -func newSnappy(level int) snappyEnc { +func newFastEnc(level int) fastEnc { switch level { case 1: - return &snappyL1{} + return &fastEncL1{} case 2: - return &snappyL2{snappyGen: snappyGen{cur: maxStoreBlockSize, prev: make([]byte, 0, maxStoreBlockSize)}} + return &fastEncL2{fastGen: fastGen{cur: maxStoreBlockSize, prev: make([]byte, 0, maxStoreBlockSize)}} case 3: - return &snappyL3{snappyGen: snappyGen{cur: maxStoreBlockSize, prev: make([]byte, 0, maxStoreBlockSize)}} + return &fastEncL3{fastGen: fastGen{cur: maxStoreBlockSize, prev: make([]byte, 0, maxStoreBlockSize)}} case 4: - return &snappyL4{snappyL3{snappyGen: snappyGen{cur: maxStoreBlockSize, prev: make([]byte, 0, maxStoreBlockSize)}}} + return &fastEncL4{fastEncL3{fastGen: fastGen{cur: maxStoreBlockSize, prev: make([]byte, 0, maxStoreBlockSize)}}} default: panic("invalid level specified") } @@ -86,12 +86,12 @@ func hash(u uint32) uint32 { return (u * 0x1e35a7bd) >> tableShift } -// snappyL1 encapsulates level 1 compression -type snappyL1 struct{} +// fastEncL1 encapsulates level 1 compression +type fastEncL1 struct{} -func (e *snappyL1) Reset() {} +func (e *fastEncL1) Reset() {} -func (e *snappyL1) Encode(dst *tokens, src []byte) { +func (e *fastEncL1) Encode(dst *tokens, src []byte) { const ( inputMargin = 16 - 1 minNonLiteralBlockSize = 1 + 1 + inputMargin @@ -310,25 +310,25 @@ type tableEntry struct { offset int32 } -// snappyGen maintains the table for matches, +// fastGen maintains the table for matches, // and the previous byte block for level 2. // This is the generic implementation. -type snappyGen struct { +type fastGen struct { prev []byte cur int32 } -// snappyGen maintains the table for matches, +// fastGen maintains the table for matches, // and the previous byte block for level 2. // This is the generic implementation. -type snappyL2 struct { - snappyGen +type fastEncL2 struct { + fastGen table [tableSize]tableEntry } // EncodeL2 uses a similar algorithm to level 1, but is capable // of matching across blocks giving better compression at a small slowdown. -func (e *snappyL2) Encode(dst *tokens, src []byte) { +func (e *fastEncL2) Encode(dst *tokens, src []byte) { const ( inputMargin = 12 - 1 minNonLiteralBlockSize = 1 + 1 + inputMargin @@ -530,14 +530,14 @@ type tableEntryPrev struct { Prev tableEntry } -// snappyL3 -type snappyL3 struct { - snappyGen +// fastEncL3 +type fastEncL3 struct { + fastGen table [tableSize]tableEntryPrev } // Encode uses a similar algorithm to level 2, will check up to two candidates. -func (e *snappyL3) Encode(dst *tokens, src []byte) { +func (e *fastEncL3) Encode(dst *tokens, src []byte) { const ( inputMargin = 8 - 1 minNonLiteralBlockSize = 1 + 1 + inputMargin @@ -548,7 +548,7 @@ func (e *snappyL3) Encode(dst *tokens, src []byte) { for i := range e.table[:] { e.table[i] = tableEntryPrev{} } - e.snappyGen = snappyGen{cur: maxStoreBlockSize, prev: e.prev[:0]} + e.fastGen = fastGen{cur: maxStoreBlockSize, prev: e.prev[:0]} } // This check isn't in the Snappy implementation, but there, the caller @@ -738,14 +738,14 @@ emitRemainder: copy(e.prev, src) } -// snappyL4 -type snappyL4 struct { - snappyL3 +// fastEncL4 +type fastEncL4 struct { + fastEncL3 } // Encode uses a similar algorithm to level 3, // but will check up to two candidates if first isn't long enough. -func (e *snappyL4) Encode(dst *tokens, src []byte) { +func (e *fastEncL4) Encode(dst *tokens, src []byte) { const ( inputMargin = 8 - 3 minNonLiteralBlockSize = 1 + 1 + inputMargin @@ -757,7 +757,7 @@ func (e *snappyL4) Encode(dst *tokens, src []byte) { for i := range e.table[:] { e.table[i] = tableEntryPrev{} } - e.snappyGen = snappyGen{cur: maxStoreBlockSize, prev: e.prev[:0]} + e.fastGen = fastGen{cur: maxStoreBlockSize, prev: e.prev[:0]} } // This check isn't in the Snappy implementation, but there, the caller @@ -965,7 +965,7 @@ emitRemainder: copy(e.prev, src) } -func (e *snappyGen) matchlen(s, t int32, src []byte) int32 { +func (e *fastGen) matchlen(s, t int32, src []byte) int32 { s1 := int(s) + maxMatchLength - 4 if s1 > len(src) { s1 = len(src) @@ -1005,7 +1005,7 @@ func (e *snappyGen) matchlen(s, t int32, src []byte) int32 { } // Reset the encoding table. -func (e *snappyGen) Reset() { +func (e *fastGen) Reset() { e.prev = e.prev[:0] e.cur += maxMatchOffset } From 4c2da439fed35d57bf50a8af26ba93060a4dbe1d Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Mon, 10 Jun 2019 17:33:19 +0200 Subject: [PATCH 17/54] Use flat history --- flate/deflate.go | 4 +- flate/fast_encoder.go | 246 +++++++++++++++++++++++++----------------- 2 files changed, 152 insertions(+), 98 deletions(-) diff --git a/flate/deflate.go b/flate/deflate.go index 12e8ef286b..b817b55af6 100644 --- a/flate/deflate.go +++ b/flate/deflate.go @@ -1079,7 +1079,7 @@ func (d *compressor) storeHuff() { // storeHuff will compress and store the currently added data, // if enough has been accumulated or we at the end of the stream. // Any error that occurred will be in d.err -func (d *compressor) storeSnappy() { +func (d *compressor) storeFast() { // We only compress if we have maxStoreBlockSize. if d.windowEnd < maxStoreBlockSize { if !d.sync { @@ -1169,7 +1169,7 @@ func (d *compressor) init(w io.Writer, level int) (err error) { d.snap = newFastEnc(level) d.window = make([]byte, maxStoreBlockSize) d.fill = (*compressor).fillBlock - d.step = (*compressor).storeSnappy + d.step = (*compressor).storeFast case level == DefaultCompression: level = 5 fallthrough diff --git a/flate/fast_encoder.go b/flate/fast_encoder.go index 0488f28b19..f42bda291e 100644 --- a/flate/fast_encoder.go +++ b/flate/fast_encoder.go @@ -32,11 +32,11 @@ func newFastEnc(level int) fastEnc { case 1: return &fastEncL1{} case 2: - return &fastEncL2{fastGen: fastGen{cur: maxStoreBlockSize, prev: make([]byte, 0, maxStoreBlockSize)}} + return &fastEncL2{fastGen: fastGen{cur: maxStoreBlockSize}} case 3: - return &fastEncL3{fastGen: fastGen{cur: maxStoreBlockSize, prev: make([]byte, 0, maxStoreBlockSize)}} + return &fastEncL3{fastGen: fastGen{cur: maxStoreBlockSize}} case 4: - return &fastEncL4{fastEncL3{fastGen: fastGen{cur: maxStoreBlockSize, prev: make([]byte, 0, maxStoreBlockSize)}}} + return &fastEncL4{fastEncL3{fastGen: fastGen{cur: maxStoreBlockSize}}} default: panic("invalid level specified") } @@ -141,7 +141,7 @@ func (e *fastEncL1) Encode(dst *tokens, src []byte) { // The "skip" variable keeps track of how many bytes there are since // the last match; dividing it by 32 (ie. right-shifting by five) gives // the number of bytes to move ahead for each iteration. - const skipLog = 4 + const skipLog = 6 const baseSkip = 1 nextS := s candidate := 0 @@ -314,10 +314,32 @@ type tableEntry struct { // and the previous byte block for level 2. // This is the generic implementation. type fastGen struct { - prev []byte + hist []byte cur int32 } +func (e *fastGen) addBlock(src []byte) int32 { + // check if we have space already + if len(e.hist)+len(src) > cap(e.hist) { + if cap(e.hist) == 0 { + l := maxMatchOffset * 10 + e.hist = make([]byte, 0, l) + } else { + if cap(e.hist) < int(maxMatchOffset*2) { + panic("unexpected buffer size") + } + // Move down + offset := int32(len(e.hist)) - maxMatchOffset + copy(e.hist[0:maxMatchOffset], e.hist[offset:]) + e.cur += offset + e.hist = e.hist[:maxMatchOffset] + } + } + s := int32(len(e.hist)) + e.hist = append(e.hist, src...) + return s +} + // fastGen maintains the table for matches, // and the previous byte block for level 2. // This is the generic implementation. @@ -335,32 +357,49 @@ func (e *fastEncL2) Encode(dst *tokens, src []byte) { ) // Protect against e.cur wraparound. - if e.cur > 1<<30 { + for e.cur >= (1<<31)-(maxStoreBlockSize*2) { + if len(e.hist) == 0 { + for i := range e.table[:] { + e.table[i] = tableEntry{} + } + e.cur = maxMatchOffset + break + } + // Shift down everything in the table that isn't already too far away. + minOff := e.cur + int32(len(e.hist)) - maxMatchOffset for i := range e.table[:] { - e.table[i] = tableEntry{} + v := e.table[i].offset + if v < minOff { + v = 0 + } else { + v = v - e.cur + maxMatchOffset + } + e.table[i].offset = v } - e.cur = maxStoreBlockSize + e.cur = maxMatchOffset } + s := e.addBlock(src) + // This check isn't in the Snappy implementation, but there, the caller // instead of the callee handles this case. if len(src) < minNonLiteralBlockSize { // We do not fill the token table. // This will be picked up by caller. dst.n = uint16(len(src)) - e.cur += maxStoreBlockSize - e.prev = e.prev[:0] return } + // Override src + src = e.hist + nextEmit := s + // sLimit is when to stop looking for offset/length copies. The inputMargin // lets us use a fast path for emitLiteral in the main loop, while we are // looking for copies. sLimit := int32(len(src) - inputMargin) // nextEmit is where in src the next emitLiteral should start from. - nextEmit := int32(0) - s := int32(0) cv := load3232(src, s) nextHash := hash(cv) @@ -380,7 +419,7 @@ func (e *fastEncL2) Encode(dst *tokens, src []byte) { // The "skip" variable keeps track of how many bytes there are since // the last match; dividing it by 32 (ie. right-shifting by five) gives // the number of bytes to move ahead for each iteration. - const skipLog = 4 + const skipLog = 6 const doEvery = 2 nextS := s @@ -435,40 +474,32 @@ func (e *fastEncL2) Encode(dst *tokens, src []byte) { // Extend the 4-byte match as long as possible. t := candidate.offset - e.cur - l := e.matchlen(s+4, t+4, src) + l := e.matchlen(s+4, t+4, src) + 4 // Extend backwards - for t > 0 && nextEmit < s && src[t-1] == src[s-1] && l < maxMatchLength-4 { + tMin := s - maxMatchOffset + if tMin < 0 { + tMin = 0 + } + for t > tMin && s > nextEmit && src[t-1] == src[s-1] && l < maxMatchLength { s-- t-- l++ } - for t <= 0 && nextEmit < s && s > 0 { - off := int32(len(e.prev)) + t - 1 - if off > 0 && e.prev[off] == src[s-1] && l < maxMatchLength-4 { - s-- - t-- - l++ - continue - } - break - } - if nextEmit < s { emitLiteral(dst, src[nextEmit:s]) } // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) - dst.tokens[dst.n] = matchToken(uint32(l+4-baseMatchLength), uint32(s-t-baseMatchOffset)) + dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) dst.n++ - s = s + l + 4 + s += l nextEmit = s if s >= sLimit { - t += l // Index first pair after match end. - if int(t+4) < len(src) && t > 0 { - cv := load3232(src, t) - e.table[hash(cv)&tableMask] = tableEntry{offset: t + e.cur, val: cv} + if int(s+l+4) < len(src) { + cv := load3232(src, s) + e.table[hash(cv)&tableMask] = tableEntry{offset: s + e.cur, val: cv} } goto emitRemainder } @@ -520,9 +551,6 @@ emitRemainder: if int(nextEmit) < len(src) { emitLiteral(dst, src[nextEmit:]) } - e.cur += int32(len(src)) - e.prev = e.prev[:len(src)] - copy(e.prev, src) } type tableEntryPrev struct { @@ -544,32 +572,54 @@ func (e *fastEncL3) Encode(dst *tokens, src []byte) { ) // Protect against e.cur wraparound. - if e.cur > 1<<30 { + for e.cur >= (1<<31)-(maxStoreBlockSize) { + if len(e.hist) == 0 { + for i := range e.table[:] { + e.table[i] = tableEntryPrev{} + } + e.cur = maxMatchOffset + break + } + // Shift down everything in the table that isn't already too far away. + minOff := e.cur + int32(len(e.hist)) - maxMatchOffset for i := range e.table[:] { - e.table[i] = tableEntryPrev{} + v := e.table[i] + if v.Cur.offset < minOff { + v.Cur.offset = 0 + } else { + v.Cur.offset = v.Cur.offset - e.cur + maxMatchOffset + } + if v.Prev.offset < minOff { + v.Prev.offset = 0 + } else { + v.Prev.offset = v.Prev.offset - e.cur + maxMatchOffset + } + e.table[i] = v } - e.fastGen = fastGen{cur: maxStoreBlockSize, prev: e.prev[:0]} + e.cur = maxMatchOffset } + s := e.addBlock(src) + // This check isn't in the Snappy implementation, but there, the caller // instead of the callee handles this case. if len(src) < minNonLiteralBlockSize { // We do not fill the token table. // This will be picked up by caller. dst.n = uint16(len(src)) - e.cur += maxStoreBlockSize - e.prev = e.prev[:0] return } + // Override src + src = e.hist + nextEmit := s + // sLimit is when to stop looking for offset/length copies. The inputMargin // lets us use a fast path for emitLiteral in the main loop, while we are // looking for copies. sLimit := int32(len(src) - inputMargin) // nextEmit is where in src the next emitLiteral should start from. - nextEmit := int32(0) - s := int32(0) cv := load3232(src, s) nextHash := hash(cv) @@ -627,11 +677,6 @@ func (e *fastEncL3) Encode(dst *tokens, src []byte) { cv = now } - // A 4-byte match has been found. We'll later see if more than 4 bytes - // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit - // them as literal bytes. - emitLiteral(dst, src[nextEmit:s]) - // Call emitCopy, and then see if another emitCopy could be our next // move. Repeat until we find no match for the input immediately after // what was consumed by the last emitCopy call. @@ -646,12 +691,25 @@ func (e *fastEncL3) Encode(dst *tokens, src []byte) { // Extend the 4-byte match as long as possible. // - s += 4 - t := candidate.offset - e.cur + 4 - l := e.matchlen(s, t, src) + t := candidate.offset - e.cur + l := e.matchlen(s+4, t+4, src) + 4 + + // Extend backwards + tMin := s - maxMatchOffset + if tMin < 0 { + tMin = 0 + } + for t > tMin && s > nextEmit && src[t-1] == src[s-1] && l < maxMatchLength { + s-- + t-- + l++ + } + if nextEmit < s { + emitLiteral(dst, src[nextEmit:s]) + } // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) - dst.tokens[dst.n] = matchToken(uint32(l+4-baseMatchLength), uint32(s-t-baseMatchOffset)) + dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) dst.n++ s += l nextEmit = s @@ -733,9 +791,6 @@ emitRemainder: if int(nextEmit) < len(src) { emitLiteral(dst, src[nextEmit:]) } - e.cur += int32(len(src)) - e.prev = e.prev[:len(src)] - copy(e.prev, src) } // fastEncL4 @@ -753,32 +808,54 @@ func (e *fastEncL4) Encode(dst *tokens, src []byte) { ) // Protect against e.cur wraparound. - if e.cur > 1<<30 { + for e.cur >= (1<<31)-(maxStoreBlockSize) { + if len(e.hist) == 0 { + for i := range e.table[:] { + e.table[i] = tableEntryPrev{} + } + e.cur = maxMatchOffset + break + } + // Shift down everything in the table that isn't already too far away. + minOff := e.cur + int32(len(e.hist)) - maxMatchOffset for i := range e.table[:] { - e.table[i] = tableEntryPrev{} + v := e.table[i] + if v.Cur.offset < minOff { + v.Cur.offset = 0 + } else { + v.Cur.offset = v.Cur.offset - e.cur + maxMatchOffset + } + if v.Prev.offset < minOff { + v.Prev.offset = 0 + } else { + v.Prev.offset = v.Prev.offset - e.cur + maxMatchOffset + } + e.table[i] = v } - e.fastGen = fastGen{cur: maxStoreBlockSize, prev: e.prev[:0]} + e.cur = maxMatchOffset } + s := e.addBlock(src) + // This check isn't in the Snappy implementation, but there, the caller // instead of the callee handles this case. if len(src) < minNonLiteralBlockSize { // We do not fill the token table. // This will be picked up by caller. dst.n = uint16(len(src)) - e.cur += maxStoreBlockSize - e.prev = e.prev[:0] return } + // Override src + src = e.hist + nextEmit := s + // sLimit is when to stop looking for offset/length copies. The inputMargin // lets us use a fast path for emitLiteral in the main loop, while we are // looking for copies. sLimit := int32(len(src) - inputMargin) // nextEmit is where in src the next emitLiteral should start from. - nextEmit := int32(0) - s := int32(0) cv := load3232(src, s) nextHash := hash(cv) @@ -960,9 +1037,6 @@ emitRemainder: if int(nextEmit) < len(src) { emitLiteral(dst, src[nextEmit:]) } - e.cur += int32(len(src)) - e.prev = e.prev[:len(src)] - copy(e.prev, src) } func (e *fastGen) matchlen(s, t int32, src []byte) int32 { @@ -971,43 +1045,23 @@ func (e *fastGen) matchlen(s, t int32, src []byte) int32 { s1 = len(src) } - // If we are inside the current block - if t >= 0 { - b := src[t:] - a := src[s:s1] - // Extend the match to be as long as possible. - return int32(matchLen(a, b)) - } - - // We found a match in the previous block. - tp := int32(len(e.prev)) + t - if tp < 0 { - return 0 - } - // Extend the match to be as long as possible. - a := src[s:s1] - b := e.prev[tp:] - if len(b) > len(a) { - b = b[:len(a)] - } - n := matchLen(b, a) - // If we reached our limit, we matched everything we are - // allowed to in the previous block and we return. - if len(b) != n || int(s)+n == s1 { - return int32(n) - } - - // Continue looking for more matches in the current block. - a = src[int(s)+n : s1] - b = src[:len(a)] - return int32(matchLen(a, b) + n) + return int32(matchLen(src[s:s1], src[t:])) } // Reset the encoding table. func (e *fastGen) Reset() { - e.prev = e.prev[:0] - e.cur += maxMatchOffset + if cap(e.hist) < int(maxMatchOffset*2) { + l := maxMatchOffset * 2 + // Make it at least 1MB. + if l < 1<<20 { + l = 1 << 20 + } + e.hist = make([]byte, 0, l) + } + // We offset current position so everything will be out of reach + e.cur += maxMatchOffset + int32(len(e.hist)) + e.hist = e.hist[:0] } // matchLen returns the maximum length. From bc78eb2a814538090e6ce73b3c6d9fa06d603b14 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Mon, 10 Jun 2019 17:59:23 +0200 Subject: [PATCH 18/54] Move Level 2 -> 1, add new level 2. --- flate/fast_encoder.go | 372 ++++++++++++++++++++---------------------- 1 file changed, 181 insertions(+), 191 deletions(-) diff --git a/flate/fast_encoder.go b/flate/fast_encoder.go index f42bda291e..3d67fc7b1e 100644 --- a/flate/fast_encoder.go +++ b/flate/fast_encoder.go @@ -30,7 +30,7 @@ type fastEnc interface { func newFastEnc(level int) fastEnc { switch level { case 1: - return &fastEncL1{} + return &fastEncL1{fastGen: fastGen{cur: maxStoreBlockSize}} case 2: return &fastEncL2{fastGen: fastGen{cur: maxStoreBlockSize}} case 3: @@ -43,13 +43,18 @@ func newFastEnc(level int) fastEnc { } const ( - tableBits = 14 // Bits used in the table + tableBits = 16 // Bits used in the table tableSize = 1 << tableBits // Size of the table tableMask = tableSize - 1 // Mask for table indices. Redundant, but can eliminate bounds checks. tableShift = 32 - tableBits // Right-shift to get the tableBits most significant bits of a uint32. baseMatchOffset = 1 // The smallest match offset baseMatchLength = 3 // The smallest match length per the RFC section 3.2.5 maxMatchOffset = 1 << 15 // The largest match offset + + bTableBits = 18 // Bits used in the table + bTableSize = 1 << bTableBits // Size of the table + bTableMask = bTableSize - 1 // Mask for table indices. Redundant, but can eliminate bounds checks. + ) func load32(b []byte, i int) uint32 { @@ -86,17 +91,81 @@ func hash(u uint32) uint32 { return (u * 0x1e35a7bd) >> tableShift } -// fastEncL1 encapsulates level 1 compression -type fastEncL1 struct{} +type tableEntry struct { + val uint32 + offset int32 +} -func (e *fastEncL1) Reset() {} +// fastGen maintains the table for matches, +// and the previous byte block for level 2. +// This is the generic implementation. +type fastGen struct { + hist []byte + cur int32 +} +func (e *fastGen) addBlock(src []byte) int32 { + // check if we have space already + if len(e.hist)+len(src) > cap(e.hist) { + if cap(e.hist) == 0 { + l := maxMatchOffset * 10 + e.hist = make([]byte, 0, l) + } else { + if cap(e.hist) < int(maxMatchOffset*2) { + panic("unexpected buffer size") + } + // Move down + offset := int32(len(e.hist)) - maxMatchOffset + copy(e.hist[0:maxMatchOffset], e.hist[offset:]) + e.cur += offset + e.hist = e.hist[:maxMatchOffset] + } + } + s := int32(len(e.hist)) + e.hist = append(e.hist, src...) + return s +} + +// fastGen maintains the table for matches, +// and the previous byte block for level 2. +// This is the generic implementation. +type fastEncL1 struct { + fastGen + table [tableSize]tableEntry +} + +// EncodeL1 uses a similar algorithm to level 1 func (e *fastEncL1) Encode(dst *tokens, src []byte) { const ( - inputMargin = 16 - 1 + inputMargin = 12 - 1 minNonLiteralBlockSize = 1 + 1 + inputMargin ) + // Protect against e.cur wraparound. + for e.cur >= (1<<31)-(maxStoreBlockSize*2) { + if len(e.hist) == 0 { + for i := range e.table[:] { + e.table[i] = tableEntry{} + } + e.cur = maxMatchOffset + break + } + // Shift down everything in the table that isn't already too far away. + minOff := e.cur + int32(len(e.hist)) - maxMatchOffset + for i := range e.table[:] { + v := e.table[i].offset + if v < minOff { + v = 0 + } else { + v = v - e.cur + maxMatchOffset + } + e.table[i].offset = v + } + e.cur = maxMatchOffset + } + + s := e.addBlock(src) + // This check isn't in the Snappy implementation, but there, the caller // instead of the callee handles this case. if len(src) < minNonLiteralBlockSize { @@ -106,110 +175,55 @@ func (e *fastEncL1) Encode(dst *tokens, src []byte) { return } - // Initialize the hash table. - // - // The table element type is uint16, as s < sLimit and sLimit < len(src) - // and len(src) <= maxStoreBlockSize and maxStoreBlockSize == 65535. - var table [tableSize]uint16 + // Override src + src = e.hist + nextEmit := s // sLimit is when to stop looking for offset/length copies. The inputMargin // lets us use a fast path for emitLiteral in the main loop, while we are // looking for copies. - sLimit := len(src) - inputMargin + sLimit := int32(len(src) - inputMargin) // nextEmit is where in src the next emitLiteral should start from. - nextEmit := 0 - - // The encoded form must start with a literal, as there are no previous - // bytes to copy, so we start looking for hash matches at s == 1. - s := 1 - nextHash := hash(load32(src, s)) + cv := load3232(src, s) + nextHash := hash(cv) for { - // Copied from the C++ snappy implementation: - // - // Heuristic match skipping: If 32 bytes are scanned with no matches - // found, start looking only at every other byte. If 32 more bytes are - // scanned (or skipped), look at every third byte, etc.. When a match - // is found, immediately go back to looking at every byte. This is a - // small loss (~5% performance, ~0.1% density) for compressible data - // due to more bookkeeping, but for non-compressible data (such as - // JPEG) it's a huge win since the compressor quickly "realizes" the - // data is incompressible and doesn't bother looking for matches - // everywhere. - // - // The "skip" variable keeps track of how many bytes there are since - // the last match; dividing it by 32 (ie. right-shifting by five) gives - // the number of bytes to move ahead for each iteration. const skipLog = 6 - const baseSkip = 1 + const doEvery = 2 + nextS := s - candidate := 0 + var candidate tableEntry for { s = nextS - nextS = (s + baseSkip) + (s-nextEmit)>>skipLog + nextS = s + doEvery + (s-nextEmit)>>skipLog if nextS > sLimit { goto emitRemainder } + candidate = e.table[nextHash&tableMask] + now := load6432(src, nextS) + e.table[nextHash&tableMask] = tableEntry{offset: s + e.cur, val: cv} + nextHash = hash(uint32(now)) - candidate = int(table[nextHash&tableMask]) - table[nextHash&tableMask] = uint16(s) - n := load6432(src, int32(nextS)) - curVal := load32(src, s) - nextVal := uint32(n) - nextHash = hash(nextVal) - if s-candidate <= maxMatchOffset && curVal == load32(src, candidate) { - break - } - const skipEvery = 1 - const skipBits = skipEvery * 8 - s = nextS - curVal = nextVal - nextS += skipEvery - nextVal = uint32(n >> skipBits) - candidate = int(table[nextHash&tableMask]) - table[nextHash&tableMask] = uint16(s) - nextHash = hash(nextVal) - if s-candidate <= maxMatchOffset && curVal == load32(src, candidate) { + offset := s - (candidate.offset - e.cur) + if offset < maxMatchOffset && cv == candidate.val { break } - if skipBits*2 <= 32 { - s = nextS - curVal = nextVal - nextS += skipEvery - nextVal = uint32(n >> (skipBits * 2)) - candidate = int(table[nextHash&tableMask]) - table[nextHash&tableMask] = uint16(s) - nextHash = hash(nextVal) - if s-candidate <= maxMatchOffset && curVal == load32(src, candidate) { - break - } - } - - if skipBits*4 <= 32 { - s = nextS - curVal = nextVal - nextS += skipEvery - nextVal = uint32(n >> (skipBits * 3)) - candidate = int(table[nextHash&tableMask]) - table[nextHash&tableMask] = uint16(s) - nextHash = hash(nextVal) - if s-candidate <= maxMatchOffset && curVal == load32(src, candidate) { - break - } + // Do one right away... + cv = uint32(now) + s = nextS + nextS++ + candidate = e.table[nextHash&tableMask] + now >>= 8 + e.table[nextHash&tableMask] = tableEntry{offset: s + e.cur, val: cv} + nextHash = hash(uint32(now)) - s = nextS - curVal = nextVal - nextS += skipEvery - nextVal = uint32(n >> (skipBits * 4)) - candidate = int(table[nextHash&tableMask]) - table[nextHash&tableMask] = uint16(s) - nextHash = hash(nextVal) - if s-candidate <= maxMatchOffset && curVal == load32(src, candidate) { - break - } + offset = s - (candidate.offset - e.cur) + if offset < maxMatchOffset && cv == candidate.val { + break } + cv = uint32(now) } // A 4-byte match has been found. We'll later see if more than 4 bytes @@ -227,55 +241,53 @@ func (e *fastEncL1) Encode(dst *tokens, src []byte) { for { // Invariant: we have a 4-byte match at s, and no need to emit any // literal bytes prior to s. - base := s // Extend the 4-byte match as long as possible. - // - // This is an inlined version of Snappy's: - // s = extendMatch(src, candidate+4, s+4) - s += 4 - s1 := base + maxMatchLength - if s1 > len(src) { - s1 = len(src) + t := candidate.offset - e.cur + l := e.matchlen(s+4, t+4, src) + 4 + + // Extend backwards + tMin := s - maxMatchOffset + if tMin < 0 { + tMin = 0 } - a := src[s:s1] - b := src[candidate+4:] - l := matchLen(a, b) - s += l - // Match backwards - for base > nextEmit && candidate > 0 && s-base < maxMatchLength && src[candidate-1] == src[base-1] { - candidate-- - base-- + for t > tMin && s > nextEmit && src[t-1] == src[s-1] && l < maxMatchLength { + s-- + t-- + l++ } - - if nextEmit < base { - emitLiteral(dst, src[nextEmit:base]) + if nextEmit < s { + emitLiteral(dst, src[nextEmit:s]) } - // matchToken is flate's equivalent of Snappy's emitCopy. - dst.tokens[dst.n] = matchToken(uint32(s-base-baseMatchLength), uint32(base-candidate-baseMatchOffset)) + // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) + dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) dst.n++ + s += l nextEmit = s - if s >= sLimit { + // Index first pair after match end. + if int(s+l+4) < len(src) { + cv := load3232(src, s) + e.table[hash(cv)&tableMask] = tableEntry{offset: s + e.cur, val: cv} + } goto emitRemainder } - // Store sparse hashes inbetween, but don't bother for the last KB. - if false && s < len(src)-1024 { - // Couldn't find a combination that gave a reasonable gain. - for i := base; i < s-7; i += 6 { + // Store every second hash in-between, but offset by 1. + if false { + for i := s - l - 2; i < s-7; i += 7 { x := load6432(src, int32(i)) nextHash := hash(uint32(x)) - table[nextHash&tableMask] = uint16(i) - //nextHash = hash(uint32(x >> 8)) - //table[nextHash&tableMask] = uint16(i + 1) - //nextHash = hash(uint32(x >> 16)) - //table[nextHash&tableMask] = uint16(i + 2) - nextHash = hash(uint32(x >> 24)) - table[nextHash&tableMask] = uint16(i + 3) - //nextHash = hash(uint32(x >> 32)) - //table[nextHash&tableMask] = uint16(i + 4) + e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i, val: uint32(x)} + // Skip one + x >>= 16 + nextHash = hash(uint32(x)) + e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i + 2, val: uint32(x)} + // Skip one + x >>= 16 + nextHash = hash(uint32(x)) + e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i + 4, val: uint32(x)} } } @@ -285,14 +297,20 @@ func (e *fastEncL1) Encode(dst *tokens, src []byte) { // at s+1. At least on GOARCH=amd64, these three hash calculations // are faster as one load64 call (with some shifts) instead of // three load32 calls. - x := load64(src, s-1) - currHash := hash(uint32(x >> 8)) + x := load6432(src, s-2) + o := e.cur + s - 2 prevHash := hash(uint32(x)) - candidate = int(table[currHash&tableMask]) - table[prevHash&tableMask] = uint16(s - 1) - table[currHash&tableMask] = uint16(s) - if s-candidate > maxMatchOffset || uint32(x>>8) != load32(src, candidate) { - nextHash = hash(uint32(x >> 16)) + prevHash2 := hash(uint32(x >> 8)) + e.table[prevHash&tableMask] = tableEntry{offset: o, val: uint32(x)} + e.table[prevHash2&tableMask] = tableEntry{offset: o + 1, val: uint32(x >> 8)} + currHash := hash(uint32(x >> 16)) + candidate = e.table[currHash&tableMask] + e.table[currHash&tableMask] = tableEntry{offset: o + 2, val: uint32(x >> 16)} + + offset := s - (candidate.offset - e.cur) + if offset > maxMatchOffset || uint32(x>>16) != candidate.val { + cv = uint32(x >> 24) + nextHash = hash(cv) s++ break } @@ -300,52 +318,24 @@ func (e *fastEncL1) Encode(dst *tokens, src []byte) { } emitRemainder: - if nextEmit < len(src) { + if int(nextEmit) < len(src) { emitLiteral(dst, src[nextEmit:]) } } -type tableEntry struct { - val uint32 - offset int32 -} - -// fastGen maintains the table for matches, -// and the previous byte block for level 2. -// This is the generic implementation. -type fastGen struct { - hist []byte - cur int32 -} - -func (e *fastGen) addBlock(src []byte) int32 { - // check if we have space already - if len(e.hist)+len(src) > cap(e.hist) { - if cap(e.hist) == 0 { - l := maxMatchOffset * 10 - e.hist = make([]byte, 0, l) - } else { - if cap(e.hist) < int(maxMatchOffset*2) { - panic("unexpected buffer size") - } - // Move down - offset := int32(len(e.hist)) - maxMatchOffset - copy(e.hist[0:maxMatchOffset], e.hist[offset:]) - e.cur += offset - e.hist = e.hist[:maxMatchOffset] - } - } - s := int32(len(e.hist)) - e.hist = append(e.hist, src...) - return s -} - // fastGen maintains the table for matches, // and the previous byte block for level 2. // This is the generic implementation. type fastEncL2 struct { fastGen - table [tableSize]tableEntry + table [bTableSize]tableEntry +} + +// hash4 returns the hash of u to fit in a hash table with h bits. +// Preferably h should be a constant and should always be <32. +func hash4u(u uint32, h uint8) uint32 { + const prime4bytes = 2654435761 + return (u * prime4bytes) >> ((32 - h) & 31) } // EncodeL2 uses a similar algorithm to level 1, but is capable @@ -401,7 +391,7 @@ func (e *fastEncL2) Encode(dst *tokens, src []byte) { // nextEmit is where in src the next emitLiteral should start from. cv := load3232(src, s) - nextHash := hash(cv) + nextHash := hash4u(cv, bTableBits) for { // Copied from the C++ snappy implementation: @@ -430,10 +420,10 @@ func (e *fastEncL2) Encode(dst *tokens, src []byte) { if nextS > sLimit { goto emitRemainder } - candidate = e.table[nextHash&tableMask] + candidate = e.table[nextHash&bTableMask] now := load6432(src, nextS) - e.table[nextHash&tableMask] = tableEntry{offset: s + e.cur, val: cv} - nextHash = hash(uint32(now)) + e.table[nextHash&bTableMask] = tableEntry{offset: s + e.cur, val: cv} + nextHash = hash4u(uint32(now), bTableBits) offset := s - (candidate.offset - e.cur) if offset < maxMatchOffset && cv == candidate.val { @@ -444,10 +434,10 @@ func (e *fastEncL2) Encode(dst *tokens, src []byte) { cv = uint32(now) s = nextS nextS++ - candidate = e.table[nextHash&tableMask] + candidate = e.table[nextHash&bTableMask] now >>= 8 - e.table[nextHash&tableMask] = tableEntry{offset: s + e.cur, val: cv} - nextHash = hash(uint32(now)) + e.table[nextHash&bTableMask] = tableEntry{offset: s + e.cur, val: cv} + nextHash = hash4u(uint32(now), bTableBits) offset = s - (candidate.offset - e.cur) if offset < maxMatchOffset && cv == candidate.val { @@ -499,25 +489,25 @@ func (e *fastEncL2) Encode(dst *tokens, src []byte) { // Index first pair after match end. if int(s+l+4) < len(src) { cv := load3232(src, s) - e.table[hash(cv)&tableMask] = tableEntry{offset: s + e.cur, val: cv} + e.table[hash4u(cv, bTableBits)&bTableMask] = tableEntry{offset: s + e.cur, val: cv} } goto emitRemainder } // Store every second hash in-between, but offset by 1. - if false { + if true { for i := s - l - 2; i < s-7; i += 7 { x := load6432(src, int32(i)) - nextHash := hash(uint32(x)) - e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i, val: uint32(x)} + nextHash := hash4u(uint32(x), bTableBits) + e.table[nextHash&bTableMask] = tableEntry{offset: e.cur + i, val: uint32(x)} // Skip one x >>= 16 - nextHash = hash(uint32(x)) - e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i + 2, val: uint32(x)} + nextHash = hash4u(uint32(x), bTableBits) + e.table[nextHash&bTableMask] = tableEntry{offset: e.cur + i + 2, val: uint32(x)} // Skip one x >>= 16 - nextHash = hash(uint32(x)) - e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i + 4, val: uint32(x)} + nextHash = hash4u(uint32(x), bTableBits) + e.table[nextHash&bTableMask] = tableEntry{offset: e.cur + i + 4, val: uint32(x)} } } @@ -529,18 +519,18 @@ func (e *fastEncL2) Encode(dst *tokens, src []byte) { // three load32 calls. x := load6432(src, s-2) o := e.cur + s - 2 - prevHash := hash(uint32(x)) - prevHash2 := hash(uint32(x >> 8)) - e.table[prevHash&tableMask] = tableEntry{offset: o, val: uint32(x)} - e.table[prevHash2&tableMask] = tableEntry{offset: o + 1, val: uint32(x >> 8)} - currHash := hash(uint32(x >> 16)) - candidate = e.table[currHash&tableMask] - e.table[currHash&tableMask] = tableEntry{offset: o + 2, val: uint32(x >> 16)} + prevHash := hash4u(uint32(x), bTableBits) + prevHash2 := hash4u(uint32(x>>8), bTableBits) + e.table[prevHash&bTableMask] = tableEntry{offset: o, val: uint32(x)} + e.table[prevHash2&bTableMask] = tableEntry{offset: o + 1, val: uint32(x >> 8)} + currHash := hash4u(uint32(x>>16), bTableBits) + candidate = e.table[currHash&bTableMask] + e.table[currHash&bTableMask] = tableEntry{offset: o + 2, val: uint32(x >> 16)} offset := s - (candidate.offset - e.cur) if offset > maxMatchOffset || uint32(x>>16) != candidate.val { cv = uint32(x >> 24) - nextHash = hash(cv) + nextHash = hash4u(uint32(cv), bTableBits) s++ break } From 970835a91fecbcfa320a12255e918829bd2a3c7a Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Mon, 10 Jun 2019 20:39:47 +0200 Subject: [PATCH 19/54] Add new level 4 with dual hashing. --- flate/fast_encoder.go | 251 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 236 insertions(+), 15 deletions(-) diff --git a/flate/fast_encoder.go b/flate/fast_encoder.go index 3d67fc7b1e..e254e313bc 100644 --- a/flate/fast_encoder.go +++ b/flate/fast_encoder.go @@ -5,7 +5,10 @@ package flate -import "math/bits" +import ( + "fmt" + "math/bits" +) // emitLiteral writes a literal chunk and returns the number of bytes written. func emitLiteral(dst *tokens, lit []byte) { @@ -36,7 +39,9 @@ func newFastEnc(level int) fastEnc { case 3: return &fastEncL3{fastGen: fastGen{cur: maxStoreBlockSize}} case 4: - return &fastEncL4{fastEncL3{fastGen: fastGen{cur: maxStoreBlockSize}}} + return &fastEncL4{fastGen: fastGen{cur: maxStoreBlockSize}} + case 5: + return &fastEncL5{fastEncL3{fastGen: fastGen{cur: maxStoreBlockSize}}} default: panic("invalid level specified") } @@ -57,6 +62,15 @@ const ( ) +const ( + prime3bytes = 506832829 + prime4bytes = 2654435761 + prime5bytes = 889523592379 + prime6bytes = 227718039650203 + prime7bytes = 58295818150454627 + prime8bytes = 0xcf1bbcdcb7a56463 +) + func load32(b []byte, i int) uint32 { // Help the compiler eliminate bounds checks on the read so it can be done in a single read. b = b[i:] @@ -207,6 +221,7 @@ func (e *fastEncL1) Encode(dst *tokens, src []byte) { offset := s - (candidate.offset - e.cur) if offset < maxMatchOffset && cv == candidate.val { + e.table[nextHash&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(now)} break } @@ -221,6 +236,7 @@ func (e *fastEncL1) Encode(dst *tokens, src []byte) { offset = s - (candidate.offset - e.cur) if offset < maxMatchOffset && cv == candidate.val { + e.table[nextHash&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(now)} break } cv = uint32(now) @@ -229,15 +245,6 @@ func (e *fastEncL1) Encode(dst *tokens, src []byte) { // A 4-byte match has been found. We'll later see if more than 4 bytes // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit // them as literal bytes. - - // Call emitCopy, and then see if another emitCopy could be our next - // move. Repeat until we find no match for the input immediately after - // what was consumed by the last emitCopy call. - // - // If we exit this loop normally then we need to call emitLiteral next, - // though we don't yet know how big the literal will be. We handle that - // by proceeding to the next iteration of the main loop. We also can - // exit this loop via goto if we get close to exhausting the input. for { // Invariant: we have a 4-byte match at s, and no need to emit any // literal bytes prior to s. @@ -334,7 +341,6 @@ type fastEncL2 struct { // hash4 returns the hash of u to fit in a hash table with h bits. // Preferably h should be a constant and should always be <32. func hash4u(u uint32, h uint8) uint32 { - const prime4bytes = 2654435761 return (u * prime4bytes) >> ((32 - h) & 31) } @@ -427,6 +433,7 @@ func (e *fastEncL2) Encode(dst *tokens, src []byte) { offset := s - (candidate.offset - e.cur) if offset < maxMatchOffset && cv == candidate.val { + e.table[nextHash&bTableMask] = tableEntry{offset: nextS + e.cur, val: uint32(now)} break } @@ -441,6 +448,7 @@ func (e *fastEncL2) Encode(dst *tokens, src []byte) { offset = s - (candidate.offset - e.cur) if offset < maxMatchOffset && cv == candidate.val { + e.table[nextHash&bTableMask] = tableEntry{offset: nextS + e.cur, val: uint32(now)} break } cv = uint32(now) @@ -496,7 +504,7 @@ func (e *fastEncL2) Encode(dst *tokens, src []byte) { // Store every second hash in-between, but offset by 1. if true { - for i := s - l - 2; i < s-7; i += 7 { + for i := s - l + 2; i < s-5; i += 7 { x := load6432(src, int32(i)) nextHash := hash4u(uint32(x), bTableBits) e.table[nextHash&bTableMask] = tableEntry{offset: e.cur + i, val: uint32(x)} @@ -783,14 +791,227 @@ emitRemainder: } } -// fastEncL4 type fastEncL4 struct { + fastGen + table [tableSize]tableEntry + bTable [bTableSize]tableEntry +} + +// hash4x64 returns the hash of the lowest 4 bytes of u to fit in a hash table with h bits. +// Preferably h should be a constant and should always be <32. +func hash4x64(u uint64, h uint8) uint32 { + return (uint32(u) * prime4bytes) >> ((32 - h) & 31) +} + +// hash7 returns the hash of the lowest 7 bytes of u to fit in a hash table with h bits. +// Preferably h should be a constant and should always be <64. +func hash7(u uint64, h uint8) uint32 { + return uint32(((u << (64 - 56)) * prime7bytes) >> ((64 - h) & 63)) +} + +func (e *fastEncL4) Encode(dst *tokens, src []byte) { + const ( + inputMargin = 12 - 1 + minNonLiteralBlockSize = 1 + 1 + inputMargin + ) + + // Protect against e.cur wraparound. + for e.cur >= (1<<31)-(maxStoreBlockSize*2) { + if len(e.hist) == 0 { + for i := range e.table[:] { + e.table[i] = tableEntry{} + } + for i := range e.bTable[:] { + e.table[i] = tableEntry{} + } + e.cur = maxMatchOffset + break + } + // Shift down everything in the table that isn't already too far away. + minOff := e.cur + int32(len(e.hist)) - maxMatchOffset + for i := range e.table[:] { + v := e.table[i].offset + if v < minOff { + v = 0 + } else { + v = v - e.cur + maxMatchOffset + } + e.table[i].offset = v + } + for i := range e.bTable[:] { + v := e.bTable[i].offset + if v < minOff { + v = 0 + } else { + v = v - e.cur + maxMatchOffset + } + e.bTable[i].offset = v + } + e.cur = maxMatchOffset + } + + s := e.addBlock(src) + + // This check isn't in the Snappy implementation, but there, the caller + // instead of the callee handles this case. + if len(src) < minNonLiteralBlockSize { + // We do not fill the token table. + // This will be picked up by caller. + dst.n = uint16(len(src)) + return + } + + // Override src + src = e.hist + nextEmit := s + + // sLimit is when to stop looking for offset/length copies. The inputMargin + // lets us use a fast path for emitLiteral in the main loop, while we are + // looking for copies. + sLimit := int32(len(src) - inputMargin) + + // nextEmit is where in src the next emitLiteral should start from. + cv := load6432(src, s) + nextHashS := hash4x64(cv, tableBits) + nextHashL := hash7(cv, bTableBits) + + for { + const skipLog = 6 + const doEvery = 1 + + nextS := s + var t int32 + for { + s = nextS + nextS = s + doEvery + (s-nextEmit)>>skipLog + if nextS > sLimit { + goto emitRemainder + } + // Fetch a short+long candidate + sCandidate := e.table[nextHashS] + lCandidate := e.bTable[nextHashL] + next := load6432(src, nextS) + entry := tableEntry{offset: s + e.cur, val: uint32(cv)} + e.table[nextHashS&tableMask] = entry + e.bTable[nextHashL&bTableMask] = entry + + nextHashS = hash4x64(next, tableBits) + nextHashL = hash7(next, bTableBits) + + t = lCandidate.offset - e.cur + if s-t < maxMatchOffset && uint32(cv) == lCandidate.val { + // Store the next match + e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + e.bTable[nextHashL&bTableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + break + } + + t = sCandidate.offset - e.cur + if s-t < maxMatchOffset && uint32(cv) == sCandidate.val { + // Found a 4 match... + lCandidate = e.bTable[nextHashL] + // Store the next match + e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + e.bTable[nextHashL&bTableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + + // If the next long is a candidate, use that... + if nextS-(lCandidate.offset-e.cur) < maxMatchOffset && lCandidate.val == uint32(next) { + s = nextS + t = lCandidate.offset - e.cur + } + break + } + cv = next + } + + // A 4-byte match has been found. We'll later see if more than 4 bytes + // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit + // them as literal bytes. + + // Extend the 4-byte match as long as possible. + l := e.matchlen(s+4, t+4, src) + 4 + + // Extend backwards + tMin := s - maxMatchOffset + if tMin < 0 { + tMin = 0 + } + for t > tMin && s > nextEmit && src[t-1] == src[s-1] && l < maxMatchLength { + s-- + t-- + l++ + } + if nextEmit < s { + emitLiteral(dst, src[nextEmit:s]) + } + if false { + if t >= s { + panic("s-t") + } + if l > maxMatchLength { + panic("mml") + } + if (s - t) > maxMatchOffset { + panic(fmt.Sprintln("mmo", t)) + } + if l < baseMatchLength { + panic("bml") + } + } + + // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) + dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) + dst.n++ + s += l + nextEmit = s + if s >= sLimit { + // Index first pair after match end. + if int(s+8) < len(src) { + cv := load6432(src, s) + e.table[hash4x64(cv, tableBits)&tableMask] = tableEntry{offset: s + e.cur, val: uint32(cv)} + e.bTable[hash7(cv, bTableBits)&bTableMask] = tableEntry{offset: s + e.cur, val: uint32(cv)} + } + goto emitRemainder + } + + // Store every 4th hash in-between + if true { + for i := s - l + 4; i < s-2; i += 4 { + cv := load6432(src, i) + t := tableEntry{offset: i + e.cur, val: uint32(cv)} + e.table[hash4x64(cv, tableBits)&tableMask] = t + e.bTable[hash7(cv, bTableBits)&bTableMask] = t + } + } + + // We could immediately start working at s now, but to improve + // compression we first update the hash table at s-1 and at s. + x := load6432(src, s-1) + o := e.cur + s - 1 + prevHashS := hash4x64(x, tableBits) + prevHashL := hash7(x, bTableBits) + e.table[prevHashS&tableMask] = tableEntry{offset: o, val: uint32(x)} + e.bTable[prevHashL&bTableMask] = tableEntry{offset: o, val: uint32(x)} + x >>= 8 + nextHashS = hash4x64(x, tableBits) + nextHashL = hash7(x, bTableBits) + cv = x + } + +emitRemainder: + if int(nextEmit) < len(src) { + emitLiteral(dst, src[nextEmit:]) + } +} + +// fastEncL5 +type fastEncL5 struct { fastEncL3 } // Encode uses a similar algorithm to level 3, // but will check up to two candidates if first isn't long enough. -func (e *fastEncL4) Encode(dst *tokens, src []byte) { +func (e *fastEncL5) Encode(dst *tokens, src []byte) { const ( inputMargin = 8 - 3 minNonLiteralBlockSize = 1 + 1 + inputMargin From 0a41eb6fddf9e46373799a622b75c356ef39d5ac Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Mon, 10 Jun 2019 23:39:30 +0200 Subject: [PATCH 20/54] Add dedicated level 5 compression. --- flate/deflate.go | 10 +- flate/fast_encoder.go | 314 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 303 insertions(+), 21 deletions(-) diff --git a/flate/deflate.go b/flate/deflate.go index b817b55af6..5e54b140d1 100644 --- a/flate/deflate.go +++ b/flate/deflate.go @@ -1165,15 +1165,15 @@ func (d *compressor) init(w io.Writer, level int) (err error) { d.window = make([]byte, maxStoreBlockSize) d.fill = (*compressor).fillBlock d.step = (*compressor).storeHuff - case level >= 1 && level <= 4: + case level == DefaultCompression: + level = 5 + fallthrough + case level >= 1 && level <= 5: d.snap = newFastEnc(level) d.window = make([]byte, maxStoreBlockSize) d.fill = (*compressor).fillBlock d.step = (*compressor).storeFast - case level == DefaultCompression: - level = 5 - fallthrough - case 5 <= level && level <= 9: + case 6 <= level && level <= 9: d.state = &advancedState{} d.compressionLevel = levels[level] d.initDeflate() diff --git a/flate/fast_encoder.go b/flate/fast_encoder.go index e254e313bc..1d9bc0567c 100644 --- a/flate/fast_encoder.go +++ b/flate/fast_encoder.go @@ -41,7 +41,7 @@ func newFastEnc(level int) fastEnc { case 4: return &fastEncL4{fastGen: fastGen{cur: maxStoreBlockSize}} case 5: - return &fastEncL5{fastEncL3{fastGen: fastGen{cur: maxStoreBlockSize}}} + return &fastEncL5{fastGen: fastGen{cur: maxStoreBlockSize}} default: panic("invalid level specified") } @@ -272,6 +272,9 @@ func (e *fastEncL1) Encode(dst *tokens, src []byte) { dst.n++ s += l nextEmit = s + if nextS >= s { + s = nextS + 1 + } if s >= sLimit { // Index first pair after match end. if int(s+l+4) < len(src) { @@ -493,6 +496,10 @@ func (e *fastEncL2) Encode(dst *tokens, src []byte) { dst.n++ s += l nextEmit = s + if nextS >= s { + s = nextS + 1 + } + if s >= sLimit { // Index first pair after match end. if int(s+l+4) < len(src) { @@ -711,6 +718,10 @@ func (e *fastEncL3) Encode(dst *tokens, src []byte) { dst.n++ s += l nextEmit = s + if nextS >= s { + s = nextS + 1 + } + if s >= sLimit { t += l // Index first pair after match end. @@ -794,7 +805,7 @@ emitRemainder: type fastEncL4 struct { fastGen table [tableSize]tableEntry - bTable [bTableSize]tableEntry + bTable [tableSize]tableEntry } // hash4x64 returns the hash of the lowest 4 bytes of u to fit in a hash table with h bits. @@ -873,7 +884,7 @@ func (e *fastEncL4) Encode(dst *tokens, src []byte) { // nextEmit is where in src the next emitLiteral should start from. cv := load6432(src, s) nextHashS := hash4x64(cv, tableBits) - nextHashL := hash7(cv, bTableBits) + nextHashL := hash7(cv, tableBits) for { const skipLog = 6 @@ -888,21 +899,21 @@ func (e *fastEncL4) Encode(dst *tokens, src []byte) { goto emitRemainder } // Fetch a short+long candidate - sCandidate := e.table[nextHashS] - lCandidate := e.bTable[nextHashL] + sCandidate := e.table[nextHashS&tableMask] + lCandidate := e.bTable[nextHashL&tableMask] next := load6432(src, nextS) entry := tableEntry{offset: s + e.cur, val: uint32(cv)} e.table[nextHashS&tableMask] = entry - e.bTable[nextHashL&bTableMask] = entry + e.bTable[nextHashL&tableMask] = entry nextHashS = hash4x64(next, tableBits) - nextHashL = hash7(next, bTableBits) + nextHashL = hash7(next, tableBits) t = lCandidate.offset - e.cur if s-t < maxMatchOffset && uint32(cv) == lCandidate.val { // Store the next match e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} - e.bTable[nextHashL&bTableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + e.bTable[nextHashL&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} break } @@ -912,7 +923,7 @@ func (e *fastEncL4) Encode(dst *tokens, src []byte) { lCandidate = e.bTable[nextHashL] // Store the next match e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} - e.bTable[nextHashL&bTableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + e.bTable[nextHashL&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} // If the next long is a candidate, use that... if nextS-(lCandidate.offset-e.cur) < maxMatchOffset && lCandidate.val == uint32(next) { @@ -964,12 +975,279 @@ func (e *fastEncL4) Encode(dst *tokens, src []byte) { dst.n++ s += l nextEmit = s + if nextS >= s { + s = nextS + 1 + } + if s >= sLimit { // Index first pair after match end. if int(s+8) < len(src) { cv := load6432(src, s) e.table[hash4x64(cv, tableBits)&tableMask] = tableEntry{offset: s + e.cur, val: uint32(cv)} - e.bTable[hash7(cv, bTableBits)&bTableMask] = tableEntry{offset: s + e.cur, val: uint32(cv)} + e.bTable[hash7(cv, tableBits)&tableMask] = tableEntry{offset: s + e.cur, val: uint32(cv)} + } + goto emitRemainder + } + + // Store every 4th hash in-between + if true { + for i := s - l + 4; i < s-2; i += 4 { + cv := load6432(src, i) + t := tableEntry{offset: i + e.cur, val: uint32(cv)} + e.table[hash4x64(cv, tableBits)&tableMask] = t + e.bTable[hash7(cv, tableBits)&tableMask] = t + } + } + + // We could immediately start working at s now, but to improve + // compression we first update the hash table at s-1 and at s. + x := load6432(src, s-1) + o := e.cur + s - 1 + prevHashS := hash4x64(x, tableBits) + prevHashL := hash7(x, tableBits) + e.table[prevHashS&tableMask] = tableEntry{offset: o, val: uint32(x)} + e.bTable[prevHashL&tableMask] = tableEntry{offset: o, val: uint32(x)} + x >>= 8 + nextHashS = hash4x64(x, tableBits) + nextHashL = hash7(x, tableBits) + cv = x + } + +emitRemainder: + if int(nextEmit) < len(src) { + emitLiteral(dst, src[nextEmit:]) + } +} + +type fastEncL5 struct { + fastGen + table [tableSize]tableEntry + bTable [tableSize]tableEntryPrev +} + +func (e *fastEncL5) Encode(dst *tokens, src []byte) { + const ( + inputMargin = 12 - 1 + minNonLiteralBlockSize = 1 + 1 + inputMargin + ) + + // Protect against e.cur wraparound. + for e.cur >= (1<<31)-(maxStoreBlockSize*2) { + if len(e.hist) == 0 { + for i := range e.table[:] { + e.table[i] = tableEntry{} + } + for i := range e.bTable[:] { + e.bTable[i] = tableEntryPrev{} + } + e.cur = maxMatchOffset + break + } + // Shift down everything in the table that isn't already too far away. + minOff := e.cur + int32(len(e.hist)) - maxMatchOffset + for i := range e.table[:] { + v := e.table[i].offset + if v < minOff { + v = 0 + } else { + v = v - e.cur + maxMatchOffset + } + e.table[i].offset = v + } + for i := range e.bTable[:] { + v := e.bTable[i] + if v.Cur.offset < minOff { + v.Cur.offset = 0 + v.Prev.offset = 0 + } else { + v.Cur.offset = v.Cur.offset - e.cur + maxMatchOffset + if v.Prev.offset < minOff { + v.Prev.offset = 0 + } else { + v.Prev.offset = v.Prev.offset - e.cur + maxMatchOffset + } + } + e.bTable[i] = v + } + e.cur = maxMatchOffset + } + + s := e.addBlock(src) + + // This check isn't in the Snappy implementation, but there, the caller + // instead of the callee handles this case. + if len(src) < minNonLiteralBlockSize { + // We do not fill the token table. + // This will be picked up by caller. + dst.n = uint16(len(src)) + return + } + + // Override src + src = e.hist + nextEmit := s + + // sLimit is when to stop looking for offset/length copies. The inputMargin + // lets us use a fast path for emitLiteral in the main loop, while we are + // looking for copies. + sLimit := int32(len(src) - inputMargin) + + // nextEmit is where in src the next emitLiteral should start from. + cv := load6432(src, s) + nextHashS := hash4x64(cv, tableBits) + nextHashL := hash7(cv, tableBits) + + for { + const skipLog = 6 + const doEvery = 1 + + nextS := s + var l int32 + var t int32 + for { + s = nextS + nextS = s + doEvery + (s-nextEmit)>>skipLog + if nextS > sLimit { + goto emitRemainder + } + // Fetch a short+long candidate + sCandidate := e.table[nextHashS&tableMask] + lCandidate := e.bTable[nextHashL&tableMask] + next := load6432(src, nextS) + entry := tableEntry{offset: s + e.cur, val: uint32(cv)} + e.table[nextHashS&tableMask] = entry + eLong := &e.bTable[nextHashL&tableMask] + eLong.Cur, eLong.Prev = entry, eLong.Cur + + nextHashS = hash4x64(next, tableBits) + nextHashL = hash7(next, tableBits) + + t = lCandidate.Cur.offset - e.cur + if s-t < maxMatchOffset { + if uint32(cv) == lCandidate.Cur.val { + // Store the next match + e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + eLong := &e.bTable[nextHashL&tableMask] + eLong.Cur, eLong.Prev = tableEntry{offset: nextS + e.cur, val: uint32(next)}, eLong.Cur + + t2 := lCandidate.Prev.offset - e.cur + if s-t2 < maxMatchOffset && uint32(cv) == lCandidate.Prev.val { + l = e.matchlen(s+4, t+4, src) + 4 + ml1 := e.matchlen(s+4, t2+4, src) + 4 + if ml1 > l { + t = t2 + l = ml1 + break + } + } + break + } + t = lCandidate.Prev.offset - e.cur + if s-t < maxMatchOffset && uint32(cv) == lCandidate.Prev.val { + // Store the next match + e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + eLong := &e.bTable[nextHashL&tableMask] + eLong.Cur, eLong.Prev = tableEntry{offset: nextS + e.cur, val: uint32(next)}, eLong.Cur + break + } + } + + t = sCandidate.offset - e.cur + if s-t < maxMatchOffset && uint32(cv) == sCandidate.val { + // Found a 4 match... + l = e.matchlen(s+4, t+4, src) + 4 + lCandidate = e.bTable[nextHashL&tableMask] + // Store the next match + + e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + eLong := &e.bTable[nextHashL&tableMask] + eLong.Cur, eLong.Prev = tableEntry{offset: nextS + e.cur, val: uint32(next)}, eLong.Cur + + // If the next long is a candidate, use that... + t2 := lCandidate.Cur.offset - e.cur + if nextS-t2 < maxMatchOffset { + if lCandidate.Cur.val == uint32(next) { + ml := e.matchlen(nextS+4, t2+4, src) + 4 + if ml > l { + t = t2 + s = nextS + l = ml + break + } + } + // If the previous long is a candidate, use that... + t2 = lCandidate.Prev.offset - e.cur + if nextS-t2 < maxMatchOffset && lCandidate.Prev.val == uint32(next) { + ml := e.matchlen(nextS+4, t2+4, src) + 4 + if ml > l { + t = t2 + s = nextS + l = ml + break + } + } + } + break + } + cv = next + } + + // A 4-byte match has been found. We'll later see if more than 4 bytes + // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit + // them as literal bytes. + + // Extend the 4-byte match as long as possible. + if l == 0 { + l = e.matchlen(s+4, t+4, src) + 4 + } + + // Extend backwards + tMin := s - maxMatchOffset + if tMin < 0 { + tMin = 0 + } + for t > tMin && s > nextEmit && src[t-1] == src[s-1] && l < maxMatchLength { + s-- + t-- + l++ + } + if nextEmit < s { + emitLiteral(dst, src[nextEmit:s]) + } + if false { + if t >= s { + panic(fmt.Sprintln("s-t", s, t)) + } + if l > maxMatchLength { + panic("mml") + } + if (s - t) > maxMatchOffset { + panic(fmt.Sprintln("mmo", s-t)) + } + if l < baseMatchLength { + panic("bml") + } + } + + // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) + dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) + dst.n++ + s += l + nextEmit = s + if nextS >= s { + s = nextS + 1 + } + if nextS >= s { + s = nextS + 1 + } + + if s >= sLimit { + // Index first pair after match end. + if false && int(s+8) < len(src) { + cv := load6432(src, s) + e.table[hash4x64(cv, tableBits)&tableMask] = tableEntry{offset: s + e.cur, val: uint32(cv)} + eLong := &e.bTable[hash7(cv, tableBits)&tableMask] + eLong.Cur, eLong.Prev = tableEntry{offset: s + e.cur, val: uint32(cv)}, eLong.Cur } goto emitRemainder } @@ -980,7 +1258,8 @@ func (e *fastEncL4) Encode(dst *tokens, src []byte) { cv := load6432(src, i) t := tableEntry{offset: i + e.cur, val: uint32(cv)} e.table[hash4x64(cv, tableBits)&tableMask] = t - e.bTable[hash7(cv, bTableBits)&bTableMask] = t + eLong := &e.bTable[hash7(cv, tableBits)&tableMask] + eLong.Cur, eLong.Prev = t, eLong.Cur } } @@ -989,12 +1268,13 @@ func (e *fastEncL4) Encode(dst *tokens, src []byte) { x := load6432(src, s-1) o := e.cur + s - 1 prevHashS := hash4x64(x, tableBits) - prevHashL := hash7(x, bTableBits) + prevHashL := hash7(x, tableBits) e.table[prevHashS&tableMask] = tableEntry{offset: o, val: uint32(x)} - e.bTable[prevHashL&bTableMask] = tableEntry{offset: o, val: uint32(x)} + eLong := &e.bTable[prevHashL&tableMask] + eLong.Cur, eLong.Prev = tableEntry{offset: o, val: uint32(x)}, eLong.Cur x >>= 8 nextHashS = hash4x64(x, tableBits) - nextHashL = hash7(x, bTableBits) + nextHashL = hash7(x, tableBits) cv = x } @@ -1004,6 +1284,7 @@ emitRemainder: } } +/* // fastEncL5 type fastEncL5 struct { fastEncL3 @@ -1249,6 +1530,7 @@ emitRemainder: emitLiteral(dst, src[nextEmit:]) } } +*/ func (e *fastGen) matchlen(s, t int32, src []byte) int32 { s1 := int(s) + maxMatchLength - 4 @@ -1262,8 +1544,8 @@ func (e *fastGen) matchlen(s, t int32, src []byte) int32 { // Reset the encoding table. func (e *fastGen) Reset() { - if cap(e.hist) < int(maxMatchOffset*2) { - l := maxMatchOffset * 2 + if cap(e.hist) < int(maxMatchOffset*8) { + l := maxMatchOffset * 8 // Make it at least 1MB. if l < 1<<20 { l = 1 << 20 From a2a0ea336a1688f5ad60c163a4459ffce8b01f9b Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Tue, 11 Jun 2019 00:46:58 +0200 Subject: [PATCH 21/54] Add a decent level 6 --- flate/deflate.go | 4 +- flate/fast_encoder.go | 281 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 283 insertions(+), 2 deletions(-) diff --git a/flate/deflate.go b/flate/deflate.go index 5e54b140d1..59fb272972 100644 --- a/flate/deflate.go +++ b/flate/deflate.go @@ -1168,12 +1168,12 @@ func (d *compressor) init(w io.Writer, level int) (err error) { case level == DefaultCompression: level = 5 fallthrough - case level >= 1 && level <= 5: + case level >= 1 && level <= 6: d.snap = newFastEnc(level) d.window = make([]byte, maxStoreBlockSize) d.fill = (*compressor).fillBlock d.step = (*compressor).storeFast - case 6 <= level && level <= 9: + case 7 <= level && level <= 9: d.state = &advancedState{} d.compressionLevel = levels[level] d.initDeflate() diff --git a/flate/fast_encoder.go b/flate/fast_encoder.go index 1d9bc0567c..4658772dd3 100644 --- a/flate/fast_encoder.go +++ b/flate/fast_encoder.go @@ -42,6 +42,8 @@ func newFastEnc(level int) fastEnc { return &fastEncL4{fastGen: fastGen{cur: maxStoreBlockSize}} case 5: return &fastEncL5{fastGen: fastGen{cur: maxStoreBlockSize}} + case 6: + return &fastEncL6{fastGen: fastGen{cur: maxStoreBlockSize}} default: panic("invalid level specified") } @@ -1284,6 +1286,285 @@ emitRemainder: } } +type fastEncL6 struct { + fastGen + table [tableSize]tableEntry + bTable [tableSize]tableEntryPrev +} + +// hash8 returns the hash of u to fit in a hash table with h bits. +// Preferably h should be a constant and should always be <64. +func hash8(u uint64, h uint8) uint32 { + return uint32((u * prime8bytes) >> ((64 - h) & 63)) +} + +// hash6 returns the hash of the lowest 6 bytes of u to fit in a hash table with h bits. +// Preferably h should be a constant and should always be <64. +func hash6(u uint64, h uint8) uint32 { + return uint32(((u << (64 - 48)) * prime6bytes) >> ((64 - h) & 63)) +} + +func (e *fastEncL6) Encode(dst *tokens, src []byte) { + const ( + inputMargin = 12 - 1 + minNonLiteralBlockSize = 1 + 1 + inputMargin + ) + + // Protect against e.cur wraparound. + for e.cur >= (1<<31)-(maxStoreBlockSize*2) { + if len(e.hist) == 0 { + for i := range e.table[:] { + e.table[i] = tableEntry{} + } + for i := range e.bTable[:] { + e.bTable[i] = tableEntryPrev{} + } + e.cur = maxMatchOffset + break + } + // Shift down everything in the table that isn't already too far away. + minOff := e.cur + int32(len(e.hist)) - maxMatchOffset + for i := range e.table[:] { + v := e.table[i].offset + if v < minOff { + v = 0 + } else { + v = v - e.cur + maxMatchOffset + } + e.table[i].offset = v + } + for i := range e.bTable[:] { + v := e.bTable[i] + if v.Cur.offset < minOff { + v.Cur.offset = 0 + v.Prev.offset = 0 + } else { + v.Cur.offset = v.Cur.offset - e.cur + maxMatchOffset + if v.Prev.offset < minOff { + v.Prev.offset = 0 + } else { + v.Prev.offset = v.Prev.offset - e.cur + maxMatchOffset + } + } + e.bTable[i] = v + } + e.cur = maxMatchOffset + } + + s := e.addBlock(src) + + // This check isn't in the Snappy implementation, but there, the caller + // instead of the callee handles this case. + if len(src) < minNonLiteralBlockSize { + // We do not fill the token table. + // This will be picked up by caller. + dst.n = uint16(len(src)) + return + } + + // Override src + src = e.hist + nextEmit := s + + // sLimit is when to stop looking for offset/length copies. The inputMargin + // lets us use a fast path for emitLiteral in the main loop, while we are + // looking for copies. + sLimit := int32(len(src) - inputMargin) + + // nextEmit is where in src the next emitLiteral should start from. + cv := load6432(src, s) + nextHashS := hash4x64(cv, tableBits) + nextHashL := hash7(cv, tableBits) + repeat := int32(0) + + for { + const skipLog = 6 + const doEvery = 1 + + nextS := s + var l int32 + var t int32 + for { + s = nextS + nextS = s + doEvery + (s-nextEmit)>>skipLog + if nextS > sLimit { + goto emitRemainder + } + // Fetch a short+long candidate + sCandidate := e.table[nextHashS&tableMask] + lCandidate := e.bTable[nextHashL&tableMask] + next := load6432(src, nextS) + entry := tableEntry{offset: s + e.cur, val: uint32(cv)} + e.table[nextHashS&tableMask] = entry + eLong := &e.bTable[nextHashL&tableMask] + eLong.Cur, eLong.Prev = entry, eLong.Cur + + nextHashS = hash4x64(next, tableBits) + nextHashL = hash7(next, tableBits) + + t = lCandidate.Cur.offset - e.cur + if s-t < maxMatchOffset { + if uint32(cv) == lCandidate.Cur.val { + // Store the next match + e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + eLong := &e.bTable[nextHashL&tableMask] + eLong.Cur, eLong.Prev = tableEntry{offset: nextS + e.cur, val: uint32(next)}, eLong.Cur + + t2 := lCandidate.Prev.offset - e.cur + if s-t2 < maxMatchOffset && uint32(cv) == lCandidate.Prev.val { + l = e.matchlen(s+4, t+4, src) + 4 + ml1 := e.matchlen(s+4, t2+4, src) + 4 + if ml1 > l { + t = t2 + l = ml1 + break + } + } + break + } + t = lCandidate.Prev.offset - e.cur + if s-t < maxMatchOffset && uint32(cv) == lCandidate.Prev.val { + // Store the next match + e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + eLong := &e.bTable[nextHashL&tableMask] + eLong.Cur, eLong.Prev = tableEntry{offset: nextS + e.cur, val: uint32(next)}, eLong.Cur + break + } + } + + t = sCandidate.offset - e.cur + if s-t < maxMatchOffset && uint32(cv) == sCandidate.val { + // Found a 4 match... + l = e.matchlen(s+4, t+4, src) + 4 + lCandidate = e.bTable[nextHashL&tableMask] + + t2 := s - repeat + if repeat > 0 && load3232(src, t2) == uint32(cv) { + ml := e.matchlen(nextS+4, t2+4, src) + 4 + if ml > l { + t = t2 + l = ml + } + } + // Store the next match + e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + eLong := &e.bTable[nextHashL&tableMask] + eLong.Cur, eLong.Prev = tableEntry{offset: nextS + e.cur, val: uint32(next)}, eLong.Cur + + // If the next long is a candidate, use that... + t2 = lCandidate.Cur.offset - e.cur + if nextS-t2 < maxMatchOffset { + if lCandidate.Cur.val == uint32(next) { + ml := e.matchlen(nextS+4, t2+4, src) + 4 + if ml > l { + t = t2 + s = nextS + l = ml + } + } + // If the previous long is a candidate, use that... + t2 = lCandidate.Prev.offset - e.cur + if nextS-t2 < maxMatchOffset && lCandidate.Prev.val == uint32(next) { + ml := e.matchlen(nextS+4, t2+4, src) + 4 + if ml > l { + t = t2 + s = nextS + l = ml + break + } + } + } + break + } + cv = next + } + + // A 4-byte match has been found. We'll later see if more than 4 bytes + // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit + // them as literal bytes. + + // Extend the 4-byte match as long as possible. + if l == 0 { + l = e.matchlen(s+4, t+4, src) + 4 + } + + // Extend backwards + tMin := s - maxMatchOffset + if tMin < 0 { + tMin = 0 + } + for t > tMin && s > nextEmit && src[t-1] == src[s-1] && l < maxMatchLength { + s-- + t-- + l++ + } + if nextEmit < s { + emitLiteral(dst, src[nextEmit:s]) + } + if false { + if t >= s { + panic(fmt.Sprintln("s-t", s, t)) + } + if l > maxMatchLength { + panic("mml") + } + if (s - t) > maxMatchOffset { + panic(fmt.Sprintln("mmo", s-t)) + } + if l < baseMatchLength { + panic("bml") + } + } + + // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) + dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) + dst.n++ + repeat = s - t + s += l + nextEmit = s + if nextS >= s { + s = nextS + 1 + } + if nextS >= s { + s = nextS + 1 + } + + if s >= sLimit { + // Index first pair after match end. + for i := nextS + 1; i < int32(len(src))-8; i += 2 { + cv := load6432(src, i) + e.table[hash4x64(cv, tableBits)&tableMask] = tableEntry{offset: i + e.cur, val: uint32(cv)} + eLong := &e.bTable[hash7(cv, tableBits)&tableMask] + eLong.Cur, eLong.Prev = tableEntry{offset: i + e.cur, val: uint32(cv)}, eLong.Cur + } + goto emitRemainder + } + + // Store every 2nd hash in-between + if true { + for i := nextS + 1; i < s; i++ { + cv := load6432(src, i) + t := tableEntry{offset: i + e.cur, val: uint32(cv)} + e.table[hash4x64(cv, tableBits)&tableMask] = t + eLong := &e.bTable[hash7(cv, tableBits)&tableMask] + eLong.Cur, eLong.Prev = t, eLong.Cur + } + } + + // We could immediately start working at s now, but to improve + // compression we first update the hash table at s-1 and at s. + x := load6432(src, s) + nextHashS = hash4x64(x, tableBits) + nextHashL = hash7(x, tableBits) + cv = x + } + +emitRemainder: + if int(nextEmit) < len(src) { + emitLiteral(dst, src[nextEmit:]) + } +} + /* // fastEncL5 type fastEncL5 struct { From 6a6c230b3129732e3d7b94733c820b3aa64fc632 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Wed, 12 Jun 2019 21:13:26 +0200 Subject: [PATCH 22/54] Move encoders to separate files. Fix level 6 problem. --- flate/fast_encoder.go | 1646 +-------------------------------- flate/flate_test.go | 72 ++ flate/level1.go | 194 ++++ flate/level2.go | 220 +++++ flate/level3.go | 240 +++++ flate/level4.go | 202 ++++ flate/level5.go | 268 ++++++ flate/level6.go | 270 ++++++ flate/testdata/regression.zip | Bin 0 -> 1879 bytes 9 files changed, 1474 insertions(+), 1638 deletions(-) create mode 100644 flate/level1.go create mode 100644 flate/level2.go create mode 100644 flate/level3.go create mode 100644 flate/level4.go create mode 100644 flate/level5.go create mode 100644 flate/level6.go create mode 100644 flate/testdata/regression.zip diff --git a/flate/fast_encoder.go b/flate/fast_encoder.go index 4658772dd3..3d2696facb 100644 --- a/flate/fast_encoder.go +++ b/flate/fast_encoder.go @@ -5,10 +5,7 @@ package flate -import ( - "fmt" - "math/bits" -) +import "math/bits" // emitLiteral writes a literal chunk and returns the number of bytes written. func emitLiteral(dst *tokens, lit []byte) { @@ -142,674 +139,17 @@ func (e *fastGen) addBlock(src []byte) int32 { return s } -// fastGen maintains the table for matches, -// and the previous byte block for level 2. -// This is the generic implementation. -type fastEncL1 struct { - fastGen - table [tableSize]tableEntry -} - -// EncodeL1 uses a similar algorithm to level 1 -func (e *fastEncL1) Encode(dst *tokens, src []byte) { - const ( - inputMargin = 12 - 1 - minNonLiteralBlockSize = 1 + 1 + inputMargin - ) - - // Protect against e.cur wraparound. - for e.cur >= (1<<31)-(maxStoreBlockSize*2) { - if len(e.hist) == 0 { - for i := range e.table[:] { - e.table[i] = tableEntry{} - } - e.cur = maxMatchOffset - break - } - // Shift down everything in the table that isn't already too far away. - minOff := e.cur + int32(len(e.hist)) - maxMatchOffset - for i := range e.table[:] { - v := e.table[i].offset - if v < minOff { - v = 0 - } else { - v = v - e.cur + maxMatchOffset - } - e.table[i].offset = v - } - e.cur = maxMatchOffset - } - - s := e.addBlock(src) - - // This check isn't in the Snappy implementation, but there, the caller - // instead of the callee handles this case. - if len(src) < minNonLiteralBlockSize { - // We do not fill the token table. - // This will be picked up by caller. - dst.n = uint16(len(src)) - return - } - - // Override src - src = e.hist - nextEmit := s - - // sLimit is when to stop looking for offset/length copies. The inputMargin - // lets us use a fast path for emitLiteral in the main loop, while we are - // looking for copies. - sLimit := int32(len(src) - inputMargin) - - // nextEmit is where in src the next emitLiteral should start from. - cv := load3232(src, s) - nextHash := hash(cv) - - for { - const skipLog = 6 - const doEvery = 2 - - nextS := s - var candidate tableEntry - for { - s = nextS - nextS = s + doEvery + (s-nextEmit)>>skipLog - if nextS > sLimit { - goto emitRemainder - } - candidate = e.table[nextHash&tableMask] - now := load6432(src, nextS) - e.table[nextHash&tableMask] = tableEntry{offset: s + e.cur, val: cv} - nextHash = hash(uint32(now)) - - offset := s - (candidate.offset - e.cur) - if offset < maxMatchOffset && cv == candidate.val { - e.table[nextHash&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(now)} - break - } - - // Do one right away... - cv = uint32(now) - s = nextS - nextS++ - candidate = e.table[nextHash&tableMask] - now >>= 8 - e.table[nextHash&tableMask] = tableEntry{offset: s + e.cur, val: cv} - nextHash = hash(uint32(now)) - - offset = s - (candidate.offset - e.cur) - if offset < maxMatchOffset && cv == candidate.val { - e.table[nextHash&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(now)} - break - } - cv = uint32(now) - } - - // A 4-byte match has been found. We'll later see if more than 4 bytes - // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit - // them as literal bytes. - for { - // Invariant: we have a 4-byte match at s, and no need to emit any - // literal bytes prior to s. - - // Extend the 4-byte match as long as possible. - t := candidate.offset - e.cur - l := e.matchlen(s+4, t+4, src) + 4 - - // Extend backwards - tMin := s - maxMatchOffset - if tMin < 0 { - tMin = 0 - } - for t > tMin && s > nextEmit && src[t-1] == src[s-1] && l < maxMatchLength { - s-- - t-- - l++ - } - if nextEmit < s { - emitLiteral(dst, src[nextEmit:s]) - } - - // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) - dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) - dst.n++ - s += l - nextEmit = s - if nextS >= s { - s = nextS + 1 - } - if s >= sLimit { - // Index first pair after match end. - if int(s+l+4) < len(src) { - cv := load3232(src, s) - e.table[hash(cv)&tableMask] = tableEntry{offset: s + e.cur, val: cv} - } - goto emitRemainder - } - - // Store every second hash in-between, but offset by 1. - if false { - for i := s - l - 2; i < s-7; i += 7 { - x := load6432(src, int32(i)) - nextHash := hash(uint32(x)) - e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i, val: uint32(x)} - // Skip one - x >>= 16 - nextHash = hash(uint32(x)) - e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i + 2, val: uint32(x)} - // Skip one - x >>= 16 - nextHash = hash(uint32(x)) - e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i + 4, val: uint32(x)} - } - } - - // We could immediately start working at s now, but to improve - // compression we first update the hash table at s-1 and at s. If - // another emitCopy is not our next move, also calculate nextHash - // at s+1. At least on GOARCH=amd64, these three hash calculations - // are faster as one load64 call (with some shifts) instead of - // three load32 calls. - x := load6432(src, s-2) - o := e.cur + s - 2 - prevHash := hash(uint32(x)) - prevHash2 := hash(uint32(x >> 8)) - e.table[prevHash&tableMask] = tableEntry{offset: o, val: uint32(x)} - e.table[prevHash2&tableMask] = tableEntry{offset: o + 1, val: uint32(x >> 8)} - currHash := hash(uint32(x >> 16)) - candidate = e.table[currHash&tableMask] - e.table[currHash&tableMask] = tableEntry{offset: o + 2, val: uint32(x >> 16)} - - offset := s - (candidate.offset - e.cur) - if offset > maxMatchOffset || uint32(x>>16) != candidate.val { - cv = uint32(x >> 24) - nextHash = hash(cv) - s++ - break - } - } - } - -emitRemainder: - if int(nextEmit) < len(src) { - emitLiteral(dst, src[nextEmit:]) - } -} - -// fastGen maintains the table for matches, -// and the previous byte block for level 2. -// This is the generic implementation. -type fastEncL2 struct { - fastGen - table [bTableSize]tableEntry -} - // hash4 returns the hash of u to fit in a hash table with h bits. // Preferably h should be a constant and should always be <32. func hash4u(u uint32, h uint8) uint32 { return (u * prime4bytes) >> ((32 - h) & 31) } -// EncodeL2 uses a similar algorithm to level 1, but is capable -// of matching across blocks giving better compression at a small slowdown. -func (e *fastEncL2) Encode(dst *tokens, src []byte) { - const ( - inputMargin = 12 - 1 - minNonLiteralBlockSize = 1 + 1 + inputMargin - ) - - // Protect against e.cur wraparound. - for e.cur >= (1<<31)-(maxStoreBlockSize*2) { - if len(e.hist) == 0 { - for i := range e.table[:] { - e.table[i] = tableEntry{} - } - e.cur = maxMatchOffset - break - } - // Shift down everything in the table that isn't already too far away. - minOff := e.cur + int32(len(e.hist)) - maxMatchOffset - for i := range e.table[:] { - v := e.table[i].offset - if v < minOff { - v = 0 - } else { - v = v - e.cur + maxMatchOffset - } - e.table[i].offset = v - } - e.cur = maxMatchOffset - } - - s := e.addBlock(src) - - // This check isn't in the Snappy implementation, but there, the caller - // instead of the callee handles this case. - if len(src) < minNonLiteralBlockSize { - // We do not fill the token table. - // This will be picked up by caller. - dst.n = uint16(len(src)) - return - } - - // Override src - src = e.hist - nextEmit := s - - // sLimit is when to stop looking for offset/length copies. The inputMargin - // lets us use a fast path for emitLiteral in the main loop, while we are - // looking for copies. - sLimit := int32(len(src) - inputMargin) - - // nextEmit is where in src the next emitLiteral should start from. - cv := load3232(src, s) - nextHash := hash4u(cv, bTableBits) - - for { - // Copied from the C++ snappy implementation: - // - // Heuristic match skipping: If 32 bytes are scanned with no matches - // found, start looking only at every other byte. If 32 more bytes are - // scanned (or skipped), look at every third byte, etc.. When a match - // is found, immediately go back to looking at every byte. This is a - // small loss (~5% performance, ~0.1% density) for compressible data - // due to more bookkeeping, but for non-compressible data (such as - // JPEG) it's a huge win since the compressor quickly "realizes" the - // data is incompressible and doesn't bother looking for matches - // everywhere. - // - // The "skip" variable keeps track of how many bytes there are since - // the last match; dividing it by 32 (ie. right-shifting by five) gives - // the number of bytes to move ahead for each iteration. - const skipLog = 6 - const doEvery = 2 - - nextS := s - var candidate tableEntry - for { - s = nextS - nextS = s + doEvery + (s-nextEmit)>>skipLog - if nextS > sLimit { - goto emitRemainder - } - candidate = e.table[nextHash&bTableMask] - now := load6432(src, nextS) - e.table[nextHash&bTableMask] = tableEntry{offset: s + e.cur, val: cv} - nextHash = hash4u(uint32(now), bTableBits) - - offset := s - (candidate.offset - e.cur) - if offset < maxMatchOffset && cv == candidate.val { - e.table[nextHash&bTableMask] = tableEntry{offset: nextS + e.cur, val: uint32(now)} - break - } - - // Do one right away... - cv = uint32(now) - s = nextS - nextS++ - candidate = e.table[nextHash&bTableMask] - now >>= 8 - e.table[nextHash&bTableMask] = tableEntry{offset: s + e.cur, val: cv} - nextHash = hash4u(uint32(now), bTableBits) - - offset = s - (candidate.offset - e.cur) - if offset < maxMatchOffset && cv == candidate.val { - e.table[nextHash&bTableMask] = tableEntry{offset: nextS + e.cur, val: uint32(now)} - break - } - cv = uint32(now) - } - - // A 4-byte match has been found. We'll later see if more than 4 bytes - // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit - // them as literal bytes. - - // Call emitCopy, and then see if another emitCopy could be our next - // move. Repeat until we find no match for the input immediately after - // what was consumed by the last emitCopy call. - // - // If we exit this loop normally then we need to call emitLiteral next, - // though we don't yet know how big the literal will be. We handle that - // by proceeding to the next iteration of the main loop. We also can - // exit this loop via goto if we get close to exhausting the input. - for { - // Invariant: we have a 4-byte match at s, and no need to emit any - // literal bytes prior to s. - - // Extend the 4-byte match as long as possible. - t := candidate.offset - e.cur - l := e.matchlen(s+4, t+4, src) + 4 - - // Extend backwards - tMin := s - maxMatchOffset - if tMin < 0 { - tMin = 0 - } - for t > tMin && s > nextEmit && src[t-1] == src[s-1] && l < maxMatchLength { - s-- - t-- - l++ - } - if nextEmit < s { - emitLiteral(dst, src[nextEmit:s]) - } - - // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) - dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) - dst.n++ - s += l - nextEmit = s - if nextS >= s { - s = nextS + 1 - } - - if s >= sLimit { - // Index first pair after match end. - if int(s+l+4) < len(src) { - cv := load3232(src, s) - e.table[hash4u(cv, bTableBits)&bTableMask] = tableEntry{offset: s + e.cur, val: cv} - } - goto emitRemainder - } - - // Store every second hash in-between, but offset by 1. - if true { - for i := s - l + 2; i < s-5; i += 7 { - x := load6432(src, int32(i)) - nextHash := hash4u(uint32(x), bTableBits) - e.table[nextHash&bTableMask] = tableEntry{offset: e.cur + i, val: uint32(x)} - // Skip one - x >>= 16 - nextHash = hash4u(uint32(x), bTableBits) - e.table[nextHash&bTableMask] = tableEntry{offset: e.cur + i + 2, val: uint32(x)} - // Skip one - x >>= 16 - nextHash = hash4u(uint32(x), bTableBits) - e.table[nextHash&bTableMask] = tableEntry{offset: e.cur + i + 4, val: uint32(x)} - } - } - - // We could immediately start working at s now, but to improve - // compression we first update the hash table at s-1 and at s. If - // another emitCopy is not our next move, also calculate nextHash - // at s+1. At least on GOARCH=amd64, these three hash calculations - // are faster as one load64 call (with some shifts) instead of - // three load32 calls. - x := load6432(src, s-2) - o := e.cur + s - 2 - prevHash := hash4u(uint32(x), bTableBits) - prevHash2 := hash4u(uint32(x>>8), bTableBits) - e.table[prevHash&bTableMask] = tableEntry{offset: o, val: uint32(x)} - e.table[prevHash2&bTableMask] = tableEntry{offset: o + 1, val: uint32(x >> 8)} - currHash := hash4u(uint32(x>>16), bTableBits) - candidate = e.table[currHash&bTableMask] - e.table[currHash&bTableMask] = tableEntry{offset: o + 2, val: uint32(x >> 16)} - - offset := s - (candidate.offset - e.cur) - if offset > maxMatchOffset || uint32(x>>16) != candidate.val { - cv = uint32(x >> 24) - nextHash = hash4u(uint32(cv), bTableBits) - s++ - break - } - } - } - -emitRemainder: - if int(nextEmit) < len(src) { - emitLiteral(dst, src[nextEmit:]) - } -} - type tableEntryPrev struct { Cur tableEntry Prev tableEntry } -// fastEncL3 -type fastEncL3 struct { - fastGen - table [tableSize]tableEntryPrev -} - -// Encode uses a similar algorithm to level 2, will check up to two candidates. -func (e *fastEncL3) Encode(dst *tokens, src []byte) { - const ( - inputMargin = 8 - 1 - minNonLiteralBlockSize = 1 + 1 + inputMargin - ) - - // Protect against e.cur wraparound. - for e.cur >= (1<<31)-(maxStoreBlockSize) { - if len(e.hist) == 0 { - for i := range e.table[:] { - e.table[i] = tableEntryPrev{} - } - e.cur = maxMatchOffset - break - } - // Shift down everything in the table that isn't already too far away. - minOff := e.cur + int32(len(e.hist)) - maxMatchOffset - for i := range e.table[:] { - v := e.table[i] - if v.Cur.offset < minOff { - v.Cur.offset = 0 - } else { - v.Cur.offset = v.Cur.offset - e.cur + maxMatchOffset - } - if v.Prev.offset < minOff { - v.Prev.offset = 0 - } else { - v.Prev.offset = v.Prev.offset - e.cur + maxMatchOffset - } - e.table[i] = v - } - e.cur = maxMatchOffset - } - - s := e.addBlock(src) - - // This check isn't in the Snappy implementation, but there, the caller - // instead of the callee handles this case. - if len(src) < minNonLiteralBlockSize { - // We do not fill the token table. - // This will be picked up by caller. - dst.n = uint16(len(src)) - return - } - - // Override src - src = e.hist - nextEmit := s - - // sLimit is when to stop looking for offset/length copies. The inputMargin - // lets us use a fast path for emitLiteral in the main loop, while we are - // looking for copies. - sLimit := int32(len(src) - inputMargin) - - // nextEmit is where in src the next emitLiteral should start from. - cv := load3232(src, s) - nextHash := hash(cv) - - for { - // Copied from the C++ snappy implementation: - // - // Heuristic match skipping: If 32 bytes are scanned with no matches - // found, start looking only at every other byte. If 32 more bytes are - // scanned (or skipped), look at every third byte, etc.. When a match - // is found, immediately go back to looking at every byte. This is a - // small loss (~5% performance, ~0.1% density) for compressible data - // due to more bookkeeping, but for non-compressible data (such as - // JPEG) it's a huge win since the compressor quickly "realizes" the - // data is incompressible and doesn't bother looking for matches - // everywhere. - // - // The "skip" variable keeps track of how many bytes there are since - // the last match; dividing it by 32 (ie. right-shifting by five) gives - // the number of bytes to move ahead for each iteration. - skip := int32(32) - - nextS := s - var candidate tableEntry - for { - s = nextS - bytesBetweenHashLookups := skip >> 5 - nextS = s + bytesBetweenHashLookups - skip += bytesBetweenHashLookups - if nextS > sLimit { - goto emitRemainder - } - candidates := e.table[nextHash&tableMask] - now := load3232(src, nextS) - e.table[nextHash&tableMask] = tableEntryPrev{Prev: candidates.Cur, Cur: tableEntry{offset: s + e.cur, val: cv}} - nextHash = hash(now) - - // Check both candidates - candidate = candidates.Cur - if cv == candidate.val { - offset := s - (candidate.offset - e.cur) - if offset <= maxMatchOffset { - break - } - } else { - // We only check if value mismatches. - // Offset will always be invalid in other cases. - candidate = candidates.Prev - if cv == candidate.val { - offset := s - (candidate.offset - e.cur) - if offset <= maxMatchOffset { - break - } - } - } - cv = now - } - - // Call emitCopy, and then see if another emitCopy could be our next - // move. Repeat until we find no match for the input immediately after - // what was consumed by the last emitCopy call. - // - // If we exit this loop normally then we need to call emitLiteral next, - // though we don't yet know how big the literal will be. We handle that - // by proceeding to the next iteration of the main loop. We also can - // exit this loop via goto if we get close to exhausting the input. - for { - // Invariant: we have a 4-byte match at s, and no need to emit any - // literal bytes prior to s. - - // Extend the 4-byte match as long as possible. - // - t := candidate.offset - e.cur - l := e.matchlen(s+4, t+4, src) + 4 - - // Extend backwards - tMin := s - maxMatchOffset - if tMin < 0 { - tMin = 0 - } - for t > tMin && s > nextEmit && src[t-1] == src[s-1] && l < maxMatchLength { - s-- - t-- - l++ - } - if nextEmit < s { - emitLiteral(dst, src[nextEmit:s]) - } - - // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) - dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) - dst.n++ - s += l - nextEmit = s - if nextS >= s { - s = nextS + 1 - } - - if s >= sLimit { - t += l - // Index first pair after match end. - if int(t+4) < len(src) && t > 0 { - cv := load3232(src, t) - nextHash = hash(cv) - e.table[nextHash&tableMask] = tableEntryPrev{ - Prev: e.table[nextHash&tableMask].Cur, - Cur: tableEntry{offset: e.cur + t, val: cv}, - } - } - goto emitRemainder - } - - // We could immediately start working at s now, but to improve - // compression we first update the hash table at s-3 to s. If - // another emitCopy is not our next move, also calculate nextHash - // at s+1. At least on GOARCH=amd64, these three hash calculations - // are faster as one load64 call (with some shifts) instead of - // three load32 calls. - x := load6432(src, s-3) - prevHash := hash(uint32(x)) - e.table[prevHash&tableMask] = tableEntryPrev{ - Prev: e.table[prevHash&tableMask].Cur, - Cur: tableEntry{offset: e.cur + s - 3, val: uint32(x)}, - } - x >>= 8 - prevHash = hash(uint32(x)) - - e.table[prevHash&tableMask] = tableEntryPrev{ - Prev: e.table[prevHash&tableMask].Cur, - Cur: tableEntry{offset: e.cur + s - 2, val: uint32(x)}, - } - x >>= 8 - prevHash = hash(uint32(x)) - - e.table[prevHash&tableMask] = tableEntryPrev{ - Prev: e.table[prevHash&tableMask].Cur, - Cur: tableEntry{offset: e.cur + s - 1, val: uint32(x)}, - } - x >>= 8 - currHash := hash(uint32(x)) - candidates := e.table[currHash&tableMask] - cv = uint32(x) - e.table[currHash&tableMask] = tableEntryPrev{ - Prev: candidates.Cur, - Cur: tableEntry{offset: s + e.cur, val: cv}, - } - - // Check both candidates - candidate = candidates.Cur - if cv == candidate.val { - offset := s - (candidate.offset - e.cur) - if offset <= maxMatchOffset { - continue - } - } else { - // We only check if value mismatches. - // Offset will always be invalid in other cases. - candidate = candidates.Prev - if cv == candidate.val { - offset := s - (candidate.offset - e.cur) - if offset <= maxMatchOffset { - continue - } - } - } - cv = uint32(x >> 8) - nextHash = hash(cv) - s++ - break - } - } - -emitRemainder: - if int(nextEmit) < len(src) { - emitLiteral(dst, src[nextEmit:]) - } -} - -type fastEncL4 struct { - fastGen - table [tableSize]tableEntry - bTable [tableSize]tableEntry -} - // hash4x64 returns the hash of the lowest 4 bytes of u to fit in a hash table with h bits. // Preferably h should be a constant and should always be <32. func hash4x64(u uint64, h uint8) uint32 { @@ -822,476 +162,6 @@ func hash7(u uint64, h uint8) uint32 { return uint32(((u << (64 - 56)) * prime7bytes) >> ((64 - h) & 63)) } -func (e *fastEncL4) Encode(dst *tokens, src []byte) { - const ( - inputMargin = 12 - 1 - minNonLiteralBlockSize = 1 + 1 + inputMargin - ) - - // Protect against e.cur wraparound. - for e.cur >= (1<<31)-(maxStoreBlockSize*2) { - if len(e.hist) == 0 { - for i := range e.table[:] { - e.table[i] = tableEntry{} - } - for i := range e.bTable[:] { - e.table[i] = tableEntry{} - } - e.cur = maxMatchOffset - break - } - // Shift down everything in the table that isn't already too far away. - minOff := e.cur + int32(len(e.hist)) - maxMatchOffset - for i := range e.table[:] { - v := e.table[i].offset - if v < minOff { - v = 0 - } else { - v = v - e.cur + maxMatchOffset - } - e.table[i].offset = v - } - for i := range e.bTable[:] { - v := e.bTable[i].offset - if v < minOff { - v = 0 - } else { - v = v - e.cur + maxMatchOffset - } - e.bTable[i].offset = v - } - e.cur = maxMatchOffset - } - - s := e.addBlock(src) - - // This check isn't in the Snappy implementation, but there, the caller - // instead of the callee handles this case. - if len(src) < minNonLiteralBlockSize { - // We do not fill the token table. - // This will be picked up by caller. - dst.n = uint16(len(src)) - return - } - - // Override src - src = e.hist - nextEmit := s - - // sLimit is when to stop looking for offset/length copies. The inputMargin - // lets us use a fast path for emitLiteral in the main loop, while we are - // looking for copies. - sLimit := int32(len(src) - inputMargin) - - // nextEmit is where in src the next emitLiteral should start from. - cv := load6432(src, s) - nextHashS := hash4x64(cv, tableBits) - nextHashL := hash7(cv, tableBits) - - for { - const skipLog = 6 - const doEvery = 1 - - nextS := s - var t int32 - for { - s = nextS - nextS = s + doEvery + (s-nextEmit)>>skipLog - if nextS > sLimit { - goto emitRemainder - } - // Fetch a short+long candidate - sCandidate := e.table[nextHashS&tableMask] - lCandidate := e.bTable[nextHashL&tableMask] - next := load6432(src, nextS) - entry := tableEntry{offset: s + e.cur, val: uint32(cv)} - e.table[nextHashS&tableMask] = entry - e.bTable[nextHashL&tableMask] = entry - - nextHashS = hash4x64(next, tableBits) - nextHashL = hash7(next, tableBits) - - t = lCandidate.offset - e.cur - if s-t < maxMatchOffset && uint32(cv) == lCandidate.val { - // Store the next match - e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} - e.bTable[nextHashL&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} - break - } - - t = sCandidate.offset - e.cur - if s-t < maxMatchOffset && uint32(cv) == sCandidate.val { - // Found a 4 match... - lCandidate = e.bTable[nextHashL] - // Store the next match - e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} - e.bTable[nextHashL&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} - - // If the next long is a candidate, use that... - if nextS-(lCandidate.offset-e.cur) < maxMatchOffset && lCandidate.val == uint32(next) { - s = nextS - t = lCandidate.offset - e.cur - } - break - } - cv = next - } - - // A 4-byte match has been found. We'll later see if more than 4 bytes - // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit - // them as literal bytes. - - // Extend the 4-byte match as long as possible. - l := e.matchlen(s+4, t+4, src) + 4 - - // Extend backwards - tMin := s - maxMatchOffset - if tMin < 0 { - tMin = 0 - } - for t > tMin && s > nextEmit && src[t-1] == src[s-1] && l < maxMatchLength { - s-- - t-- - l++ - } - if nextEmit < s { - emitLiteral(dst, src[nextEmit:s]) - } - if false { - if t >= s { - panic("s-t") - } - if l > maxMatchLength { - panic("mml") - } - if (s - t) > maxMatchOffset { - panic(fmt.Sprintln("mmo", t)) - } - if l < baseMatchLength { - panic("bml") - } - } - - // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) - dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) - dst.n++ - s += l - nextEmit = s - if nextS >= s { - s = nextS + 1 - } - - if s >= sLimit { - // Index first pair after match end. - if int(s+8) < len(src) { - cv := load6432(src, s) - e.table[hash4x64(cv, tableBits)&tableMask] = tableEntry{offset: s + e.cur, val: uint32(cv)} - e.bTable[hash7(cv, tableBits)&tableMask] = tableEntry{offset: s + e.cur, val: uint32(cv)} - } - goto emitRemainder - } - - // Store every 4th hash in-between - if true { - for i := s - l + 4; i < s-2; i += 4 { - cv := load6432(src, i) - t := tableEntry{offset: i + e.cur, val: uint32(cv)} - e.table[hash4x64(cv, tableBits)&tableMask] = t - e.bTable[hash7(cv, tableBits)&tableMask] = t - } - } - - // We could immediately start working at s now, but to improve - // compression we first update the hash table at s-1 and at s. - x := load6432(src, s-1) - o := e.cur + s - 1 - prevHashS := hash4x64(x, tableBits) - prevHashL := hash7(x, tableBits) - e.table[prevHashS&tableMask] = tableEntry{offset: o, val: uint32(x)} - e.bTable[prevHashL&tableMask] = tableEntry{offset: o, val: uint32(x)} - x >>= 8 - nextHashS = hash4x64(x, tableBits) - nextHashL = hash7(x, tableBits) - cv = x - } - -emitRemainder: - if int(nextEmit) < len(src) { - emitLiteral(dst, src[nextEmit:]) - } -} - -type fastEncL5 struct { - fastGen - table [tableSize]tableEntry - bTable [tableSize]tableEntryPrev -} - -func (e *fastEncL5) Encode(dst *tokens, src []byte) { - const ( - inputMargin = 12 - 1 - minNonLiteralBlockSize = 1 + 1 + inputMargin - ) - - // Protect against e.cur wraparound. - for e.cur >= (1<<31)-(maxStoreBlockSize*2) { - if len(e.hist) == 0 { - for i := range e.table[:] { - e.table[i] = tableEntry{} - } - for i := range e.bTable[:] { - e.bTable[i] = tableEntryPrev{} - } - e.cur = maxMatchOffset - break - } - // Shift down everything in the table that isn't already too far away. - minOff := e.cur + int32(len(e.hist)) - maxMatchOffset - for i := range e.table[:] { - v := e.table[i].offset - if v < minOff { - v = 0 - } else { - v = v - e.cur + maxMatchOffset - } - e.table[i].offset = v - } - for i := range e.bTable[:] { - v := e.bTable[i] - if v.Cur.offset < minOff { - v.Cur.offset = 0 - v.Prev.offset = 0 - } else { - v.Cur.offset = v.Cur.offset - e.cur + maxMatchOffset - if v.Prev.offset < minOff { - v.Prev.offset = 0 - } else { - v.Prev.offset = v.Prev.offset - e.cur + maxMatchOffset - } - } - e.bTable[i] = v - } - e.cur = maxMatchOffset - } - - s := e.addBlock(src) - - // This check isn't in the Snappy implementation, but there, the caller - // instead of the callee handles this case. - if len(src) < minNonLiteralBlockSize { - // We do not fill the token table. - // This will be picked up by caller. - dst.n = uint16(len(src)) - return - } - - // Override src - src = e.hist - nextEmit := s - - // sLimit is when to stop looking for offset/length copies. The inputMargin - // lets us use a fast path for emitLiteral in the main loop, while we are - // looking for copies. - sLimit := int32(len(src) - inputMargin) - - // nextEmit is where in src the next emitLiteral should start from. - cv := load6432(src, s) - nextHashS := hash4x64(cv, tableBits) - nextHashL := hash7(cv, tableBits) - - for { - const skipLog = 6 - const doEvery = 1 - - nextS := s - var l int32 - var t int32 - for { - s = nextS - nextS = s + doEvery + (s-nextEmit)>>skipLog - if nextS > sLimit { - goto emitRemainder - } - // Fetch a short+long candidate - sCandidate := e.table[nextHashS&tableMask] - lCandidate := e.bTable[nextHashL&tableMask] - next := load6432(src, nextS) - entry := tableEntry{offset: s + e.cur, val: uint32(cv)} - e.table[nextHashS&tableMask] = entry - eLong := &e.bTable[nextHashL&tableMask] - eLong.Cur, eLong.Prev = entry, eLong.Cur - - nextHashS = hash4x64(next, tableBits) - nextHashL = hash7(next, tableBits) - - t = lCandidate.Cur.offset - e.cur - if s-t < maxMatchOffset { - if uint32(cv) == lCandidate.Cur.val { - // Store the next match - e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} - eLong := &e.bTable[nextHashL&tableMask] - eLong.Cur, eLong.Prev = tableEntry{offset: nextS + e.cur, val: uint32(next)}, eLong.Cur - - t2 := lCandidate.Prev.offset - e.cur - if s-t2 < maxMatchOffset && uint32(cv) == lCandidate.Prev.val { - l = e.matchlen(s+4, t+4, src) + 4 - ml1 := e.matchlen(s+4, t2+4, src) + 4 - if ml1 > l { - t = t2 - l = ml1 - break - } - } - break - } - t = lCandidate.Prev.offset - e.cur - if s-t < maxMatchOffset && uint32(cv) == lCandidate.Prev.val { - // Store the next match - e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} - eLong := &e.bTable[nextHashL&tableMask] - eLong.Cur, eLong.Prev = tableEntry{offset: nextS + e.cur, val: uint32(next)}, eLong.Cur - break - } - } - - t = sCandidate.offset - e.cur - if s-t < maxMatchOffset && uint32(cv) == sCandidate.val { - // Found a 4 match... - l = e.matchlen(s+4, t+4, src) + 4 - lCandidate = e.bTable[nextHashL&tableMask] - // Store the next match - - e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} - eLong := &e.bTable[nextHashL&tableMask] - eLong.Cur, eLong.Prev = tableEntry{offset: nextS + e.cur, val: uint32(next)}, eLong.Cur - - // If the next long is a candidate, use that... - t2 := lCandidate.Cur.offset - e.cur - if nextS-t2 < maxMatchOffset { - if lCandidate.Cur.val == uint32(next) { - ml := e.matchlen(nextS+4, t2+4, src) + 4 - if ml > l { - t = t2 - s = nextS - l = ml - break - } - } - // If the previous long is a candidate, use that... - t2 = lCandidate.Prev.offset - e.cur - if nextS-t2 < maxMatchOffset && lCandidate.Prev.val == uint32(next) { - ml := e.matchlen(nextS+4, t2+4, src) + 4 - if ml > l { - t = t2 - s = nextS - l = ml - break - } - } - } - break - } - cv = next - } - - // A 4-byte match has been found. We'll later see if more than 4 bytes - // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit - // them as literal bytes. - - // Extend the 4-byte match as long as possible. - if l == 0 { - l = e.matchlen(s+4, t+4, src) + 4 - } - - // Extend backwards - tMin := s - maxMatchOffset - if tMin < 0 { - tMin = 0 - } - for t > tMin && s > nextEmit && src[t-1] == src[s-1] && l < maxMatchLength { - s-- - t-- - l++ - } - if nextEmit < s { - emitLiteral(dst, src[nextEmit:s]) - } - if false { - if t >= s { - panic(fmt.Sprintln("s-t", s, t)) - } - if l > maxMatchLength { - panic("mml") - } - if (s - t) > maxMatchOffset { - panic(fmt.Sprintln("mmo", s-t)) - } - if l < baseMatchLength { - panic("bml") - } - } - - // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) - dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) - dst.n++ - s += l - nextEmit = s - if nextS >= s { - s = nextS + 1 - } - if nextS >= s { - s = nextS + 1 - } - - if s >= sLimit { - // Index first pair after match end. - if false && int(s+8) < len(src) { - cv := load6432(src, s) - e.table[hash4x64(cv, tableBits)&tableMask] = tableEntry{offset: s + e.cur, val: uint32(cv)} - eLong := &e.bTable[hash7(cv, tableBits)&tableMask] - eLong.Cur, eLong.Prev = tableEntry{offset: s + e.cur, val: uint32(cv)}, eLong.Cur - } - goto emitRemainder - } - - // Store every 4th hash in-between - if true { - for i := s - l + 4; i < s-2; i += 4 { - cv := load6432(src, i) - t := tableEntry{offset: i + e.cur, val: uint32(cv)} - e.table[hash4x64(cv, tableBits)&tableMask] = t - eLong := &e.bTable[hash7(cv, tableBits)&tableMask] - eLong.Cur, eLong.Prev = t, eLong.Cur - } - } - - // We could immediately start working at s now, but to improve - // compression we first update the hash table at s-1 and at s. - x := load6432(src, s-1) - o := e.cur + s - 1 - prevHashS := hash4x64(x, tableBits) - prevHashL := hash7(x, tableBits) - e.table[prevHashS&tableMask] = tableEntry{offset: o, val: uint32(x)} - eLong := &e.bTable[prevHashL&tableMask] - eLong.Cur, eLong.Prev = tableEntry{offset: o, val: uint32(x)}, eLong.Cur - x >>= 8 - nextHashS = hash4x64(x, tableBits) - nextHashL = hash7(x, tableBits) - cv = x - } - -emitRemainder: - if int(nextEmit) < len(src) { - emitLiteral(dst, src[nextEmit:]) - } -} - -type fastEncL6 struct { - fastGen - table [tableSize]tableEntry - bTable [tableSize]tableEntryPrev -} - // hash8 returns the hash of u to fit in a hash table with h bits. // Preferably h should be a constant and should always be <64. func hash8(u uint64, h uint8) uint32 { @@ -1304,515 +174,15 @@ func hash6(u uint64, h uint8) uint32 { return uint32(((u << (64 - 48)) * prime6bytes) >> ((64 - h) & 63)) } -func (e *fastEncL6) Encode(dst *tokens, src []byte) { - const ( - inputMargin = 12 - 1 - minNonLiteralBlockSize = 1 + 1 + inputMargin - ) - - // Protect against e.cur wraparound. - for e.cur >= (1<<31)-(maxStoreBlockSize*2) { - if len(e.hist) == 0 { - for i := range e.table[:] { - e.table[i] = tableEntry{} - } - for i := range e.bTable[:] { - e.bTable[i] = tableEntryPrev{} - } - e.cur = maxMatchOffset - break - } - // Shift down everything in the table that isn't already too far away. - minOff := e.cur + int32(len(e.hist)) - maxMatchOffset - for i := range e.table[:] { - v := e.table[i].offset - if v < minOff { - v = 0 - } else { - v = v - e.cur + maxMatchOffset - } - e.table[i].offset = v - } - for i := range e.bTable[:] { - v := e.bTable[i] - if v.Cur.offset < minOff { - v.Cur.offset = 0 - v.Prev.offset = 0 - } else { - v.Cur.offset = v.Cur.offset - e.cur + maxMatchOffset - if v.Prev.offset < minOff { - v.Prev.offset = 0 - } else { - v.Prev.offset = v.Prev.offset - e.cur + maxMatchOffset - } - } - e.bTable[i] = v - } - e.cur = maxMatchOffset - } - - s := e.addBlock(src) - - // This check isn't in the Snappy implementation, but there, the caller - // instead of the callee handles this case. - if len(src) < minNonLiteralBlockSize { - // We do not fill the token table. - // This will be picked up by caller. - dst.n = uint16(len(src)) - return - } - - // Override src - src = e.hist - nextEmit := s - - // sLimit is when to stop looking for offset/length copies. The inputMargin - // lets us use a fast path for emitLiteral in the main loop, while we are - // looking for copies. - sLimit := int32(len(src) - inputMargin) - - // nextEmit is where in src the next emitLiteral should start from. - cv := load6432(src, s) - nextHashS := hash4x64(cv, tableBits) - nextHashL := hash7(cv, tableBits) - repeat := int32(0) - - for { - const skipLog = 6 - const doEvery = 1 - - nextS := s - var l int32 - var t int32 - for { - s = nextS - nextS = s + doEvery + (s-nextEmit)>>skipLog - if nextS > sLimit { - goto emitRemainder - } - // Fetch a short+long candidate - sCandidate := e.table[nextHashS&tableMask] - lCandidate := e.bTable[nextHashL&tableMask] - next := load6432(src, nextS) - entry := tableEntry{offset: s + e.cur, val: uint32(cv)} - e.table[nextHashS&tableMask] = entry - eLong := &e.bTable[nextHashL&tableMask] - eLong.Cur, eLong.Prev = entry, eLong.Cur - - nextHashS = hash4x64(next, tableBits) - nextHashL = hash7(next, tableBits) - - t = lCandidate.Cur.offset - e.cur - if s-t < maxMatchOffset { - if uint32(cv) == lCandidate.Cur.val { - // Store the next match - e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} - eLong := &e.bTable[nextHashL&tableMask] - eLong.Cur, eLong.Prev = tableEntry{offset: nextS + e.cur, val: uint32(next)}, eLong.Cur - - t2 := lCandidate.Prev.offset - e.cur - if s-t2 < maxMatchOffset && uint32(cv) == lCandidate.Prev.val { - l = e.matchlen(s+4, t+4, src) + 4 - ml1 := e.matchlen(s+4, t2+4, src) + 4 - if ml1 > l { - t = t2 - l = ml1 - break - } - } - break - } - t = lCandidate.Prev.offset - e.cur - if s-t < maxMatchOffset && uint32(cv) == lCandidate.Prev.val { - // Store the next match - e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} - eLong := &e.bTable[nextHashL&tableMask] - eLong.Cur, eLong.Prev = tableEntry{offset: nextS + e.cur, val: uint32(next)}, eLong.Cur - break - } - } - - t = sCandidate.offset - e.cur - if s-t < maxMatchOffset && uint32(cv) == sCandidate.val { - // Found a 4 match... - l = e.matchlen(s+4, t+4, src) + 4 - lCandidate = e.bTable[nextHashL&tableMask] - - t2 := s - repeat - if repeat > 0 && load3232(src, t2) == uint32(cv) { - ml := e.matchlen(nextS+4, t2+4, src) + 4 - if ml > l { - t = t2 - l = ml - } - } - // Store the next match - e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} - eLong := &e.bTable[nextHashL&tableMask] - eLong.Cur, eLong.Prev = tableEntry{offset: nextS + e.cur, val: uint32(next)}, eLong.Cur - - // If the next long is a candidate, use that... - t2 = lCandidate.Cur.offset - e.cur - if nextS-t2 < maxMatchOffset { - if lCandidate.Cur.val == uint32(next) { - ml := e.matchlen(nextS+4, t2+4, src) + 4 - if ml > l { - t = t2 - s = nextS - l = ml - } - } - // If the previous long is a candidate, use that... - t2 = lCandidate.Prev.offset - e.cur - if nextS-t2 < maxMatchOffset && lCandidate.Prev.val == uint32(next) { - ml := e.matchlen(nextS+4, t2+4, src) + 4 - if ml > l { - t = t2 - s = nextS - l = ml - break - } - } - } - break - } - cv = next - } - - // A 4-byte match has been found. We'll later see if more than 4 bytes - // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit - // them as literal bytes. - - // Extend the 4-byte match as long as possible. - if l == 0 { - l = e.matchlen(s+4, t+4, src) + 4 - } - - // Extend backwards - tMin := s - maxMatchOffset - if tMin < 0 { - tMin = 0 - } - for t > tMin && s > nextEmit && src[t-1] == src[s-1] && l < maxMatchLength { - s-- - t-- - l++ - } - if nextEmit < s { - emitLiteral(dst, src[nextEmit:s]) - } - if false { - if t >= s { - panic(fmt.Sprintln("s-t", s, t)) - } - if l > maxMatchLength { - panic("mml") - } - if (s - t) > maxMatchOffset { - panic(fmt.Sprintln("mmo", s-t)) - } - if l < baseMatchLength { - panic("bml") - } - } - - // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) - dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) - dst.n++ - repeat = s - t - s += l - nextEmit = s - if nextS >= s { - s = nextS + 1 - } - if nextS >= s { - s = nextS + 1 - } - - if s >= sLimit { - // Index first pair after match end. - for i := nextS + 1; i < int32(len(src))-8; i += 2 { - cv := load6432(src, i) - e.table[hash4x64(cv, tableBits)&tableMask] = tableEntry{offset: i + e.cur, val: uint32(cv)} - eLong := &e.bTable[hash7(cv, tableBits)&tableMask] - eLong.Cur, eLong.Prev = tableEntry{offset: i + e.cur, val: uint32(cv)}, eLong.Cur - } - goto emitRemainder - } - - // Store every 2nd hash in-between - if true { - for i := nextS + 1; i < s; i++ { - cv := load6432(src, i) - t := tableEntry{offset: i + e.cur, val: uint32(cv)} - e.table[hash4x64(cv, tableBits)&tableMask] = t - eLong := &e.bTable[hash7(cv, tableBits)&tableMask] - eLong.Cur, eLong.Prev = t, eLong.Cur - } - } - - // We could immediately start working at s now, but to improve - // compression we first update the hash table at s-1 and at s. - x := load6432(src, s) - nextHashS = hash4x64(x, tableBits) - nextHashL = hash7(x, tableBits) - cv = x - } - -emitRemainder: - if int(nextEmit) < len(src) { - emitLiteral(dst, src[nextEmit:]) - } -} - -/* -// fastEncL5 -type fastEncL5 struct { - fastEncL3 -} - -// Encode uses a similar algorithm to level 3, -// but will check up to two candidates if first isn't long enough. -func (e *fastEncL5) Encode(dst *tokens, src []byte) { - const ( - inputMargin = 8 - 3 - minNonLiteralBlockSize = 1 + 1 + inputMargin - matchLenGood = 12 - ) - - // Protect against e.cur wraparound. - for e.cur >= (1<<31)-(maxStoreBlockSize) { - if len(e.hist) == 0 { - for i := range e.table[:] { - e.table[i] = tableEntryPrev{} - } - e.cur = maxMatchOffset - break - } - // Shift down everything in the table that isn't already too far away. - minOff := e.cur + int32(len(e.hist)) - maxMatchOffset - for i := range e.table[:] { - v := e.table[i] - if v.Cur.offset < minOff { - v.Cur.offset = 0 - } else { - v.Cur.offset = v.Cur.offset - e.cur + maxMatchOffset - } - if v.Prev.offset < minOff { - v.Prev.offset = 0 - } else { - v.Prev.offset = v.Prev.offset - e.cur + maxMatchOffset - } - e.table[i] = v - } - e.cur = maxMatchOffset - } - - s := e.addBlock(src) - - // This check isn't in the Snappy implementation, but there, the caller - // instead of the callee handles this case. - if len(src) < minNonLiteralBlockSize { - // We do not fill the token table. - // This will be picked up by caller. - dst.n = uint16(len(src)) - return - } - - // Override src - src = e.hist - nextEmit := s - - // sLimit is when to stop looking for offset/length copies. The inputMargin - // lets us use a fast path for emitLiteral in the main loop, while we are - // looking for copies. - sLimit := int32(len(src) - inputMargin) - - // nextEmit is where in src the next emitLiteral should start from. - cv := load3232(src, s) - nextHash := hash(cv) - - for { - // Copied from the C++ snappy implementation: - // - // Heuristic match skipping: If 32 bytes are scanned with no matches - // found, start looking only at every other byte. If 32 more bytes are - // scanned (or skipped), look at every third byte, etc.. When a match - // is found, immediately go back to looking at every byte. This is a - // small loss (~5% performance, ~0.1% density) for compressible data - // due to more bookkeeping, but for non-compressible data (such as - // JPEG) it's a huge win since the compressor quickly "realizes" the - // data is incompressible and doesn't bother looking for matches - // everywhere. - // - // The "skip" variable keeps track of how many bytes there are since - // the last match; dividing it by 32 (ie. right-shifting by five) gives - // the number of bytes to move ahead for each iteration. - skip := int32(32) - - nextS := s - var candidate tableEntry - var candidateAlt tableEntry - for { - s = nextS - bytesBetweenHashLookups := skip >> 5 - nextS = s + bytesBetweenHashLookups - skip += bytesBetweenHashLookups - if nextS > sLimit { - goto emitRemainder - } - candidates := e.table[nextHash&tableMask] - now := load3232(src, nextS) - e.table[nextHash&tableMask] = tableEntryPrev{Prev: candidates.Cur, Cur: tableEntry{offset: s + e.cur, val: cv}} - nextHash = hash(now) - - // Check both candidates - candidate = candidates.Cur - if cv == candidate.val { - offset := s - (candidate.offset - e.cur) - if offset < maxMatchOffset { - offset = s - (candidates.Prev.offset - e.cur) - if cv == candidates.Prev.val && offset < maxMatchOffset { - candidateAlt = candidates.Prev - } - break - } - } else { - // We only check if value mismatches. - // Offset will always be invalid in other cases. - candidate = candidates.Prev - if cv == candidate.val { - offset := s - (candidate.offset - e.cur) - if offset < maxMatchOffset { - break - } - } - } - cv = now - } - - // A 4-byte match has been found. We'll later see if more than 4 bytes - // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit - // them as literal bytes. - emitLiteral(dst, src[nextEmit:s]) - - // Call emitCopy, and then see if another emitCopy could be our next - // move. Repeat until we find no match for the input immediately after - // what was consumed by the last emitCopy call. - // - // If we exit this loop normally then we need to call emitLiteral next, - // though we don't yet know how big the literal will be. We handle that - // by proceeding to the next iteration of the main loop. We also can - // exit this loop via goto if we get close to exhausting the input. - for { - // Invariant: we have a 4-byte match at s, and no need to emit any - // literal bytes prior to s. - - // Extend the 4-byte match as long as possible. - // - s += 4 - t := candidate.offset - e.cur + 4 - l := e.matchlen(s, t, src) - // Try alternative candidate if match length < matchLenGood. - if l < matchLenGood-4 && candidateAlt.offset != 0 { - t2 := candidateAlt.offset - e.cur + 4 - l2 := e.matchlen(s, t2, src) - if l2 > l { - l = l2 - t = t2 - } - } - // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) - dst.tokens[dst.n] = matchToken(uint32(l+4-baseMatchLength), uint32(s-t-baseMatchOffset)) - dst.n++ - s += l - nextEmit = s - if s >= sLimit { - t += l - // Index first pair after match end. - if int(t+4) < len(src) && t > 0 { - cv := load3232(src, t) - nextHash = hash(cv) - e.table[nextHash&tableMask] = tableEntryPrev{ - Prev: e.table[nextHash&tableMask].Cur, - Cur: tableEntry{offset: e.cur + t, val: cv}, - } - } - goto emitRemainder - } - - // We could immediately start working at s now, but to improve - // compression we first update the hash table at s-3 to s. If - // another emitCopy is not our next move, also calculate nextHash - // at s+1. At least on GOARCH=amd64, these three hash calculations - // are faster as one load64 call (with some shifts) instead of - // three load32 calls. - x := load6432(src, s-3) - prevHash := hash(uint32(x)) - e.table[prevHash&tableMask] = tableEntryPrev{ - Prev: e.table[prevHash&tableMask].Cur, - Cur: tableEntry{offset: e.cur + s - 3, val: uint32(x)}, - } - x >>= 8 - prevHash = hash(uint32(x)) - - e.table[prevHash&tableMask] = tableEntryPrev{ - Prev: e.table[prevHash&tableMask].Cur, - Cur: tableEntry{offset: e.cur + s - 2, val: uint32(x)}, - } - x >>= 8 - prevHash = hash(uint32(x)) - - e.table[prevHash&tableMask] = tableEntryPrev{ - Prev: e.table[prevHash&tableMask].Cur, - Cur: tableEntry{offset: e.cur + s - 1, val: uint32(x)}, - } - x >>= 8 - currHash := hash(uint32(x)) - candidates := e.table[currHash&tableMask] - cv = uint32(x) - e.table[currHash&tableMask] = tableEntryPrev{ - Prev: candidates.Cur, - Cur: tableEntry{offset: s + e.cur, val: cv}, - } - - // Check both candidates - candidate = candidates.Cur - candidateAlt = tableEntry{} - if cv == candidate.val { - offset := s - (candidate.offset - e.cur) - if offset <= maxMatchOffset { - offset = s - (candidates.Prev.offset - e.cur) - if cv == candidates.Prev.val && offset <= maxMatchOffset { - candidateAlt = candidates.Prev - } - continue - } - } else { - // We only check if value mismatches. - // Offset will always be invalid in other cases. - candidate = candidates.Prev - if cv == candidate.val { - offset := s - (candidate.offset - e.cur) - if offset <= maxMatchOffset { - continue - } - } - } - cv = uint32(x >> 8) - nextHash = hash(cv) - s++ - break - } - } - -emitRemainder: - if int(nextEmit) < len(src) { - emitLiteral(dst, src[nextEmit:]) - } +type fastEncL4 struct { + fastGen + table [tableSize]tableEntry + bTable [tableSize]tableEntry } -*/ +// matchlen will return the match length between offsets and t in src. +// The maximum length returned is maxMatchLength - 4. +// It is assumed that s > t, that t >=0 and s < len(src). func (e *fastGen) matchlen(s, t int32, src []byte) int32 { s1 := int(s) + maxMatchLength - 4 if s1 > len(src) { diff --git a/flate/flate_test.go b/flate/flate_test.go index 3f67025cd7..2258484ba3 100644 --- a/flate/flate_test.go +++ b/flate/flate_test.go @@ -9,8 +9,11 @@ package flate import ( + "archive/zip" "bytes" + "compress/flate" "encoding/hex" + "fmt" "io/ioutil" "testing" ) @@ -66,6 +69,75 @@ func TestInvalidEncoding(t *testing.T) { } } +func TestRegressions(t *testing.T) { + // Test fuzzer regressions + data, err := ioutil.ReadFile("testdata/regression.zip") + if err != nil { + t.Fatal(err) + } + zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) + if err != nil { + t.Fatal(err) + } + for _, tt := range zr.File { + data, err := tt.Open() + if err != nil { + t.Fatal(err) + } + data1, err := ioutil.ReadAll(data) + for level := 0; level <= 9; level++ { + t.Run(fmt.Sprint(tt.Name+"-level", 1), func(t *testing.T) { + buf := new(bytes.Buffer) + fw, err := NewWriter(buf, level) + if err != nil { + t.Error(err) + } + n, err := fw.Write(data1) + if n != len(data1) { + t.Error("short write") + } + if err != nil { + t.Error(err) + } + err = fw.Close() + if err != nil { + t.Error(err) + } + fr1 := NewReader(buf) + data2, err := ioutil.ReadAll(fr1) + if err != nil { + t.Error(err) + } + if bytes.Compare(data1, data2) != 0 { + t.Error("not equal") + } + // Do it again... + buf.Reset() + fw.Reset(buf) + n, err = fw.Write(data1) + if n != len(data1) { + t.Error("short write") + } + if err != nil { + t.Error(err) + } + err = fw.Close() + if err != nil { + t.Error(err) + } + fr1 = flate.NewReader(buf) + data2, err = ioutil.ReadAll(fr1) + if err != nil { + t.Error(err) + } + if bytes.Compare(data1, data2) != 0 { + t.Error("not equal") + } + }) + } + } +} + func TestInvalidBits(t *testing.T) { oversubscribed := []int{1, 2, 3, 4, 4, 5} incomplete := []int{1, 2, 4, 4} diff --git a/flate/level1.go b/flate/level1.go new file mode 100644 index 0000000000..f68b568a0d --- /dev/null +++ b/flate/level1.go @@ -0,0 +1,194 @@ +package flate + +// fastGen maintains the table for matches, +// and the previous byte block for level 2. +// This is the generic implementation. +type fastEncL1 struct { + fastGen + table [tableSize]tableEntry +} + +// EncodeL1 uses a similar algorithm to level 1 +func (e *fastEncL1) Encode(dst *tokens, src []byte) { + const ( + inputMargin = 12 - 1 + minNonLiteralBlockSize = 1 + 1 + inputMargin + ) + + // Protect against e.cur wraparound. + for e.cur >= (1<<31)-(maxStoreBlockSize*2) { + if len(e.hist) == 0 { + for i := range e.table[:] { + e.table[i] = tableEntry{} + } + e.cur = maxMatchOffset + break + } + // Shift down everything in the table that isn't already too far away. + minOff := e.cur + int32(len(e.hist)) - maxMatchOffset + for i := range e.table[:] { + v := e.table[i].offset + if v < minOff { + v = 0 + } else { + v = v - e.cur + maxMatchOffset + } + e.table[i].offset = v + } + e.cur = maxMatchOffset + } + + s := e.addBlock(src) + + // This check isn't in the Snappy implementation, but there, the caller + // instead of the callee handles this case. + if len(src) < minNonLiteralBlockSize { + // We do not fill the token table. + // This will be picked up by caller. + dst.n = uint16(len(src)) + return + } + + // Override src + src = e.hist + nextEmit := s + + // sLimit is when to stop looking for offset/length copies. The inputMargin + // lets us use a fast path for emitLiteral in the main loop, while we are + // looking for copies. + sLimit := int32(len(src) - inputMargin) + + // nextEmit is where in src the next emitLiteral should start from. + cv := load3232(src, s) + nextHash := hash(cv) + + for { + const skipLog = 6 + const doEvery = 2 + + nextS := s + var candidate tableEntry + for { + s = nextS + nextS = s + doEvery + (s-nextEmit)>>skipLog + if nextS > sLimit { + goto emitRemainder + } + candidate = e.table[nextHash&tableMask] + now := load6432(src, nextS) + e.table[nextHash&tableMask] = tableEntry{offset: s + e.cur, val: cv} + nextHash = hash(uint32(now)) + + offset := s - (candidate.offset - e.cur) + if offset < maxMatchOffset && cv == candidate.val { + e.table[nextHash&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(now)} + break + } + + // Do one right away... + cv = uint32(now) + s = nextS + nextS++ + candidate = e.table[nextHash&tableMask] + now >>= 8 + e.table[nextHash&tableMask] = tableEntry{offset: s + e.cur, val: cv} + nextHash = hash(uint32(now)) + + offset = s - (candidate.offset - e.cur) + if offset < maxMatchOffset && cv == candidate.val { + e.table[nextHash&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(now)} + break + } + cv = uint32(now) + } + + // A 4-byte match has been found. We'll later see if more than 4 bytes + // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit + // them as literal bytes. + for { + // Invariant: we have a 4-byte match at s, and no need to emit any + // literal bytes prior to s. + + // Extend the 4-byte match as long as possible. + t := candidate.offset - e.cur + l := e.matchlen(s+4, t+4, src) + 4 + + // Extend backwards + tMin := s - maxMatchOffset + if tMin < 0 { + tMin = 0 + } + for t > tMin && s > nextEmit && src[t-1] == src[s-1] && l < maxMatchLength { + s-- + t-- + l++ + } + if nextEmit < s { + emitLiteral(dst, src[nextEmit:s]) + } + + // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) + dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) + dst.n++ + s += l + nextEmit = s + if nextS >= s { + s = nextS + 1 + } + if s >= sLimit { + // Index first pair after match end. + if int(s+l+4) < len(src) { + cv := load3232(src, s) + e.table[hash(cv)&tableMask] = tableEntry{offset: s + e.cur, val: cv} + } + goto emitRemainder + } + + // Store every second hash in-between, but offset by 1. + if false { + for i := s - l - 2; i < s-7; i += 7 { + x := load6432(src, int32(i)) + nextHash := hash(uint32(x)) + e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i, val: uint32(x)} + // Skip one + x >>= 16 + nextHash = hash(uint32(x)) + e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i + 2, val: uint32(x)} + // Skip one + x >>= 16 + nextHash = hash(uint32(x)) + e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i + 4, val: uint32(x)} + } + } + + // We could immediately start working at s now, but to improve + // compression we first update the hash table at s-1 and at s. If + // another emitCopy is not our next move, also calculate nextHash + // at s+1. At least on GOARCH=amd64, these three hash calculations + // are faster as one load64 call (with some shifts) instead of + // three load32 calls. + x := load6432(src, s-2) + o := e.cur + s - 2 + prevHash := hash(uint32(x)) + prevHash2 := hash(uint32(x >> 8)) + e.table[prevHash&tableMask] = tableEntry{offset: o, val: uint32(x)} + e.table[prevHash2&tableMask] = tableEntry{offset: o + 1, val: uint32(x >> 8)} + currHash := hash(uint32(x >> 16)) + candidate = e.table[currHash&tableMask] + e.table[currHash&tableMask] = tableEntry{offset: o + 2, val: uint32(x >> 16)} + + offset := s - (candidate.offset - e.cur) + if offset > maxMatchOffset || uint32(x>>16) != candidate.val { + cv = uint32(x >> 24) + nextHash = hash(cv) + s++ + break + } + } + } + +emitRemainder: + if int(nextEmit) < len(src) { + emitLiteral(dst, src[nextEmit:]) + } +} diff --git a/flate/level2.go b/flate/level2.go new file mode 100644 index 0000000000..92dda361f7 --- /dev/null +++ b/flate/level2.go @@ -0,0 +1,220 @@ +package flate + +// fastGen maintains the table for matches, +// and the previous byte block for level 2. +// This is the generic implementation. +type fastEncL2 struct { + fastGen + table [bTableSize]tableEntry +} + +// EncodeL2 uses a similar algorithm to level 1, but is capable +// of matching across blocks giving better compression at a small slowdown. +func (e *fastEncL2) Encode(dst *tokens, src []byte) { + const ( + inputMargin = 12 - 1 + minNonLiteralBlockSize = 1 + 1 + inputMargin + ) + + // Protect against e.cur wraparound. + for e.cur >= (1<<31)-(maxStoreBlockSize*2) { + if len(e.hist) == 0 { + for i := range e.table[:] { + e.table[i] = tableEntry{} + } + e.cur = maxMatchOffset + break + } + // Shift down everything in the table that isn't already too far away. + minOff := e.cur + int32(len(e.hist)) - maxMatchOffset + for i := range e.table[:] { + v := e.table[i].offset + if v < minOff { + v = 0 + } else { + v = v - e.cur + maxMatchOffset + } + e.table[i].offset = v + } + e.cur = maxMatchOffset + } + + s := e.addBlock(src) + + // This check isn't in the Snappy implementation, but there, the caller + // instead of the callee handles this case. + if len(src) < minNonLiteralBlockSize { + // We do not fill the token table. + // This will be picked up by caller. + dst.n = uint16(len(src)) + return + } + + // Override src + src = e.hist + nextEmit := s + + // sLimit is when to stop looking for offset/length copies. The inputMargin + // lets us use a fast path for emitLiteral in the main loop, while we are + // looking for copies. + sLimit := int32(len(src) - inputMargin) + + // nextEmit is where in src the next emitLiteral should start from. + cv := load3232(src, s) + nextHash := hash4u(cv, bTableBits) + + for { + // Copied from the C++ snappy implementation: + // + // Heuristic match skipping: If 32 bytes are scanned with no matches + // found, start looking only at every other byte. If 32 more bytes are + // scanned (or skipped), look at every third byte, etc.. When a match + // is found, immediately go back to looking at every byte. This is a + // small loss (~5% performance, ~0.1% density) for compressible data + // due to more bookkeeping, but for non-compressible data (such as + // JPEG) it's a huge win since the compressor quickly "realizes" the + // data is incompressible and doesn't bother looking for matches + // everywhere. + // + // The "skip" variable keeps track of how many bytes there are since + // the last match; dividing it by 32 (ie. right-shifting by five) gives + // the number of bytes to move ahead for each iteration. + const skipLog = 6 + const doEvery = 2 + + nextS := s + var candidate tableEntry + for { + s = nextS + nextS = s + doEvery + (s-nextEmit)>>skipLog + if nextS > sLimit { + goto emitRemainder + } + candidate = e.table[nextHash&bTableMask] + now := load6432(src, nextS) + e.table[nextHash&bTableMask] = tableEntry{offset: s + e.cur, val: cv} + nextHash = hash4u(uint32(now), bTableBits) + + offset := s - (candidate.offset - e.cur) + if offset < maxMatchOffset && cv == candidate.val { + e.table[nextHash&bTableMask] = tableEntry{offset: nextS + e.cur, val: uint32(now)} + break + } + + // Do one right away... + cv = uint32(now) + s = nextS + nextS++ + candidate = e.table[nextHash&bTableMask] + now >>= 8 + e.table[nextHash&bTableMask] = tableEntry{offset: s + e.cur, val: cv} + nextHash = hash4u(uint32(now), bTableBits) + + offset = s - (candidate.offset - e.cur) + if offset < maxMatchOffset && cv == candidate.val { + e.table[nextHash&bTableMask] = tableEntry{offset: nextS + e.cur, val: uint32(now)} + break + } + cv = uint32(now) + } + + // A 4-byte match has been found. We'll later see if more than 4 bytes + // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit + // them as literal bytes. + + // Call emitCopy, and then see if another emitCopy could be our next + // move. Repeat until we find no match for the input immediately after + // what was consumed by the last emitCopy call. + // + // If we exit this loop normally then we need to call emitLiteral next, + // though we don't yet know how big the literal will be. We handle that + // by proceeding to the next iteration of the main loop. We also can + // exit this loop via goto if we get close to exhausting the input. + for { + // Invariant: we have a 4-byte match at s, and no need to emit any + // literal bytes prior to s. + + // Extend the 4-byte match as long as possible. + t := candidate.offset - e.cur + l := e.matchlen(s+4, t+4, src) + 4 + + // Extend backwards + tMin := s - maxMatchOffset + if tMin < 0 { + tMin = 0 + } + for t > tMin && s > nextEmit && src[t-1] == src[s-1] && l < maxMatchLength { + s-- + t-- + l++ + } + if nextEmit < s { + emitLiteral(dst, src[nextEmit:s]) + } + + // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) + dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) + dst.n++ + s += l + nextEmit = s + if nextS >= s { + s = nextS + 1 + } + + if s >= sLimit { + // Index first pair after match end. + if int(s+l+4) < len(src) { + cv := load3232(src, s) + e.table[hash4u(cv, bTableBits)&bTableMask] = tableEntry{offset: s + e.cur, val: cv} + } + goto emitRemainder + } + + // Store every second hash in-between, but offset by 1. + if true { + for i := s - l + 2; i < s-5; i += 7 { + x := load6432(src, int32(i)) + nextHash := hash4u(uint32(x), bTableBits) + e.table[nextHash&bTableMask] = tableEntry{offset: e.cur + i, val: uint32(x)} + // Skip one + x >>= 16 + nextHash = hash4u(uint32(x), bTableBits) + e.table[nextHash&bTableMask] = tableEntry{offset: e.cur + i + 2, val: uint32(x)} + // Skip one + x >>= 16 + nextHash = hash4u(uint32(x), bTableBits) + e.table[nextHash&bTableMask] = tableEntry{offset: e.cur + i + 4, val: uint32(x)} + } + } + + // We could immediately start working at s now, but to improve + // compression we first update the hash table at s-1 and at s. If + // another emitCopy is not our next move, also calculate nextHash + // at s+1. At least on GOARCH=amd64, these three hash calculations + // are faster as one load64 call (with some shifts) instead of + // three load32 calls. + x := load6432(src, s-2) + o := e.cur + s - 2 + prevHash := hash4u(uint32(x), bTableBits) + prevHash2 := hash4u(uint32(x>>8), bTableBits) + e.table[prevHash&bTableMask] = tableEntry{offset: o, val: uint32(x)} + e.table[prevHash2&bTableMask] = tableEntry{offset: o + 1, val: uint32(x >> 8)} + currHash := hash4u(uint32(x>>16), bTableBits) + candidate = e.table[currHash&bTableMask] + e.table[currHash&bTableMask] = tableEntry{offset: o + 2, val: uint32(x >> 16)} + + offset := s - (candidate.offset - e.cur) + if offset > maxMatchOffset || uint32(x>>16) != candidate.val { + cv = uint32(x >> 24) + nextHash = hash4u(uint32(cv), bTableBits) + s++ + break + } + } + } + +emitRemainder: + if int(nextEmit) < len(src) { + emitLiteral(dst, src[nextEmit:]) + } +} diff --git a/flate/level3.go b/flate/level3.go new file mode 100644 index 0000000000..36217d5377 --- /dev/null +++ b/flate/level3.go @@ -0,0 +1,240 @@ +package flate + +// fastEncL3 +type fastEncL3 struct { + fastGen + table [tableSize]tableEntryPrev +} + +// Encode uses a similar algorithm to level 2, will check up to two candidates. +func (e *fastEncL3) Encode(dst *tokens, src []byte) { + const ( + inputMargin = 8 - 1 + minNonLiteralBlockSize = 1 + 1 + inputMargin + ) + + // Protect against e.cur wraparound. + for e.cur >= (1<<31)-(maxStoreBlockSize) { + if len(e.hist) == 0 { + for i := range e.table[:] { + e.table[i] = tableEntryPrev{} + } + e.cur = maxMatchOffset + break + } + // Shift down everything in the table that isn't already too far away. + minOff := e.cur + int32(len(e.hist)) - maxMatchOffset + for i := range e.table[:] { + v := e.table[i] + if v.Cur.offset < minOff { + v.Cur.offset = 0 + } else { + v.Cur.offset = v.Cur.offset - e.cur + maxMatchOffset + } + if v.Prev.offset < minOff { + v.Prev.offset = 0 + } else { + v.Prev.offset = v.Prev.offset - e.cur + maxMatchOffset + } + e.table[i] = v + } + e.cur = maxMatchOffset + } + + s := e.addBlock(src) + + // This check isn't in the Snappy implementation, but there, the caller + // instead of the callee handles this case. + if len(src) < minNonLiteralBlockSize { + // We do not fill the token table. + // This will be picked up by caller. + dst.n = uint16(len(src)) + return + } + + // Override src + src = e.hist + nextEmit := s + + // sLimit is when to stop looking for offset/length copies. The inputMargin + // lets us use a fast path for emitLiteral in the main loop, while we are + // looking for copies. + sLimit := int32(len(src) - inputMargin) + + // nextEmit is where in src the next emitLiteral should start from. + cv := load3232(src, s) + nextHash := hash(cv) + + for { + // Copied from the C++ snappy implementation: + // + // Heuristic match skipping: If 32 bytes are scanned with no matches + // found, start looking only at every other byte. If 32 more bytes are + // scanned (or skipped), look at every third byte, etc.. When a match + // is found, immediately go back to looking at every byte. This is a + // small loss (~5% performance, ~0.1% density) for compressible data + // due to more bookkeeping, but for non-compressible data (such as + // JPEG) it's a huge win since the compressor quickly "realizes" the + // data is incompressible and doesn't bother looking for matches + // everywhere. + // + // The "skip" variable keeps track of how many bytes there are since + // the last match; dividing it by 32 (ie. right-shifting by five) gives + // the number of bytes to move ahead for each iteration. + skip := int32(32) + + nextS := s + var candidate tableEntry + for { + s = nextS + bytesBetweenHashLookups := skip >> 5 + nextS = s + bytesBetweenHashLookups + skip += bytesBetweenHashLookups + if nextS > sLimit { + goto emitRemainder + } + candidates := e.table[nextHash&tableMask] + now := load3232(src, nextS) + e.table[nextHash&tableMask] = tableEntryPrev{Prev: candidates.Cur, Cur: tableEntry{offset: s + e.cur, val: cv}} + nextHash = hash(now) + + // Check both candidates + candidate = candidates.Cur + if cv == candidate.val { + offset := s - (candidate.offset - e.cur) + if offset <= maxMatchOffset { + break + } + } else { + // We only check if value mismatches. + // Offset will always be invalid in other cases. + candidate = candidates.Prev + if cv == candidate.val { + offset := s - (candidate.offset - e.cur) + if offset <= maxMatchOffset { + break + } + } + } + cv = now + } + + // Call emitCopy, and then see if another emitCopy could be our next + // move. Repeat until we find no match for the input immediately after + // what was consumed by the last emitCopy call. + // + // If we exit this loop normally then we need to call emitLiteral next, + // though we don't yet know how big the literal will be. We handle that + // by proceeding to the next iteration of the main loop. We also can + // exit this loop via goto if we get close to exhausting the input. + for { + // Invariant: we have a 4-byte match at s, and no need to emit any + // literal bytes prior to s. + + // Extend the 4-byte match as long as possible. + // + t := candidate.offset - e.cur + l := e.matchlen(s+4, t+4, src) + 4 + + // Extend backwards + tMin := s - maxMatchOffset + if tMin < 0 { + tMin = 0 + } + for t > tMin && s > nextEmit && src[t-1] == src[s-1] && l < maxMatchLength { + s-- + t-- + l++ + } + if nextEmit < s { + emitLiteral(dst, src[nextEmit:s]) + } + + // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) + dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) + dst.n++ + s += l + nextEmit = s + if nextS >= s { + s = nextS + 1 + } + + if s >= sLimit { + t += l + // Index first pair after match end. + if int(t+4) < len(src) && t > 0 { + cv := load3232(src, t) + nextHash = hash(cv) + e.table[nextHash&tableMask] = tableEntryPrev{ + Prev: e.table[nextHash&tableMask].Cur, + Cur: tableEntry{offset: e.cur + t, val: cv}, + } + } + goto emitRemainder + } + + // We could immediately start working at s now, but to improve + // compression we first update the hash table at s-3 to s. If + // another emitCopy is not our next move, also calculate nextHash + // at s+1. At least on GOARCH=amd64, these three hash calculations + // are faster as one load64 call (with some shifts) instead of + // three load32 calls. + x := load6432(src, s-3) + prevHash := hash(uint32(x)) + e.table[prevHash&tableMask] = tableEntryPrev{ + Prev: e.table[prevHash&tableMask].Cur, + Cur: tableEntry{offset: e.cur + s - 3, val: uint32(x)}, + } + x >>= 8 + prevHash = hash(uint32(x)) + + e.table[prevHash&tableMask] = tableEntryPrev{ + Prev: e.table[prevHash&tableMask].Cur, + Cur: tableEntry{offset: e.cur + s - 2, val: uint32(x)}, + } + x >>= 8 + prevHash = hash(uint32(x)) + + e.table[prevHash&tableMask] = tableEntryPrev{ + Prev: e.table[prevHash&tableMask].Cur, + Cur: tableEntry{offset: e.cur + s - 1, val: uint32(x)}, + } + x >>= 8 + currHash := hash(uint32(x)) + candidates := e.table[currHash&tableMask] + cv = uint32(x) + e.table[currHash&tableMask] = tableEntryPrev{ + Prev: candidates.Cur, + Cur: tableEntry{offset: s + e.cur, val: cv}, + } + + // Check both candidates + candidate = candidates.Cur + if cv == candidate.val { + offset := s - (candidate.offset - e.cur) + if offset <= maxMatchOffset { + continue + } + } else { + // We only check if value mismatches. + // Offset will always be invalid in other cases. + candidate = candidates.Prev + if cv == candidate.val { + offset := s - (candidate.offset - e.cur) + if offset <= maxMatchOffset { + continue + } + } + } + cv = uint32(x >> 8) + nextHash = hash(cv) + s++ + break + } + } + +emitRemainder: + if int(nextEmit) < len(src) { + emitLiteral(dst, src[nextEmit:]) + } +} diff --git a/flate/level4.go b/flate/level4.go new file mode 100644 index 0000000000..a3cd514150 --- /dev/null +++ b/flate/level4.go @@ -0,0 +1,202 @@ +package flate + +import "fmt" + +func (e *fastEncL4) Encode(dst *tokens, src []byte) { + const ( + inputMargin = 12 - 1 + minNonLiteralBlockSize = 1 + 1 + inputMargin + ) + + // Protect against e.cur wraparound. + for e.cur >= (1<<31)-(maxStoreBlockSize*2) { + if len(e.hist) == 0 { + for i := range e.table[:] { + e.table[i] = tableEntry{} + } + for i := range e.bTable[:] { + e.table[i] = tableEntry{} + } + e.cur = maxMatchOffset + break + } + // Shift down everything in the table that isn't already too far away. + minOff := e.cur + int32(len(e.hist)) - maxMatchOffset + for i := range e.table[:] { + v := e.table[i].offset + if v < minOff { + v = 0 + } else { + v = v - e.cur + maxMatchOffset + } + e.table[i].offset = v + } + for i := range e.bTable[:] { + v := e.bTable[i].offset + if v < minOff { + v = 0 + } else { + v = v - e.cur + maxMatchOffset + } + e.bTable[i].offset = v + } + e.cur = maxMatchOffset + } + + s := e.addBlock(src) + + // This check isn't in the Snappy implementation, but there, the caller + // instead of the callee handles this case. + if len(src) < minNonLiteralBlockSize { + // We do not fill the token table. + // This will be picked up by caller. + dst.n = uint16(len(src)) + return + } + + // Override src + src = e.hist + nextEmit := s + + // sLimit is when to stop looking for offset/length copies. The inputMargin + // lets us use a fast path for emitLiteral in the main loop, while we are + // looking for copies. + sLimit := int32(len(src) - inputMargin) + + // nextEmit is where in src the next emitLiteral should start from. + cv := load6432(src, s) + nextHashS := hash4x64(cv, tableBits) + nextHashL := hash7(cv, tableBits) + + for { + const skipLog = 6 + const doEvery = 1 + + nextS := s + var t int32 + for { + s = nextS + nextS = s + doEvery + (s-nextEmit)>>skipLog + if nextS > sLimit { + goto emitRemainder + } + // Fetch a short+long candidate + sCandidate := e.table[nextHashS&tableMask] + lCandidate := e.bTable[nextHashL&tableMask] + next := load6432(src, nextS) + entry := tableEntry{offset: s + e.cur, val: uint32(cv)} + e.table[nextHashS&tableMask] = entry + e.bTable[nextHashL&tableMask] = entry + + nextHashS = hash4x64(next, tableBits) + nextHashL = hash7(next, tableBits) + + t = lCandidate.offset - e.cur + if s-t < maxMatchOffset && uint32(cv) == lCandidate.val { + // Store the next match + e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + e.bTable[nextHashL&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + break + } + + t = sCandidate.offset - e.cur + if s-t < maxMatchOffset && uint32(cv) == sCandidate.val { + // Found a 4 match... + lCandidate = e.bTable[nextHashL] + // Store the next match + e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + e.bTable[nextHashL&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + + // If the next long is a candidate, use that... + if nextS-(lCandidate.offset-e.cur) < maxMatchOffset && lCandidate.val == uint32(next) { + s = nextS + t = lCandidate.offset - e.cur + } + break + } + cv = next + } + + // A 4-byte match has been found. We'll later see if more than 4 bytes + // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit + // them as literal bytes. + + // Extend the 4-byte match as long as possible. + l := e.matchlen(s+4, t+4, src) + 4 + + // Extend backwards + tMin := s - maxMatchOffset + if tMin < 0 { + tMin = 0 + } + for t > tMin && s > nextEmit && src[t-1] == src[s-1] && l < maxMatchLength { + s-- + t-- + l++ + } + if nextEmit < s { + emitLiteral(dst, src[nextEmit:s]) + } + if false { + if t >= s { + panic("s-t") + } + if l > maxMatchLength { + panic("mml") + } + if (s - t) > maxMatchOffset { + panic(fmt.Sprintln("mmo", t)) + } + if l < baseMatchLength { + panic("bml") + } + } + + // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) + dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) + dst.n++ + s += l + nextEmit = s + if nextS >= s { + s = nextS + 1 + } + + if s >= sLimit { + // Index first pair after match end. + if int(s+8) < len(src) { + cv := load6432(src, s) + e.table[hash4x64(cv, tableBits)&tableMask] = tableEntry{offset: s + e.cur, val: uint32(cv)} + e.bTable[hash7(cv, tableBits)&tableMask] = tableEntry{offset: s + e.cur, val: uint32(cv)} + } + goto emitRemainder + } + + // Store every 4th hash in-between + if true { + for i := s - l + 4; i < s-2; i += 4 { + cv := load6432(src, i) + t := tableEntry{offset: i + e.cur, val: uint32(cv)} + e.table[hash4x64(cv, tableBits)&tableMask] = t + e.bTable[hash7(cv, tableBits)&tableMask] = t + } + } + + // We could immediately start working at s now, but to improve + // compression we first update the hash table at s-1 and at s. + x := load6432(src, s-1) + o := e.cur + s - 1 + prevHashS := hash4x64(x, tableBits) + prevHashL := hash7(x, tableBits) + e.table[prevHashS&tableMask] = tableEntry{offset: o, val: uint32(x)} + e.bTable[prevHashL&tableMask] = tableEntry{offset: o, val: uint32(x)} + x >>= 8 + nextHashS = hash4x64(x, tableBits) + nextHashL = hash7(x, tableBits) + cv = x + } + +emitRemainder: + if int(nextEmit) < len(src) { + emitLiteral(dst, src[nextEmit:]) + } +} diff --git a/flate/level5.go b/flate/level5.go new file mode 100644 index 0000000000..0c25d2bfc8 --- /dev/null +++ b/flate/level5.go @@ -0,0 +1,268 @@ +package flate + +import "fmt" + +type fastEncL5 struct { + fastGen + table [tableSize]tableEntry + bTable [tableSize]tableEntryPrev +} + +func (e *fastEncL5) Encode(dst *tokens, src []byte) { + const ( + inputMargin = 12 - 1 + minNonLiteralBlockSize = 1 + 1 + inputMargin + ) + + // Protect against e.cur wraparound. + for e.cur >= (1<<31)-(maxStoreBlockSize*2) { + if len(e.hist) == 0 { + for i := range e.table[:] { + e.table[i] = tableEntry{} + } + for i := range e.bTable[:] { + e.bTable[i] = tableEntryPrev{} + } + e.cur = maxMatchOffset + break + } + // Shift down everything in the table that isn't already too far away. + minOff := e.cur + int32(len(e.hist)) - maxMatchOffset + for i := range e.table[:] { + v := e.table[i].offset + if v < minOff { + v = 0 + } else { + v = v - e.cur + maxMatchOffset + } + e.table[i].offset = v + } + for i := range e.bTable[:] { + v := e.bTable[i] + if v.Cur.offset < minOff { + v.Cur.offset = 0 + v.Prev.offset = 0 + } else { + v.Cur.offset = v.Cur.offset - e.cur + maxMatchOffset + if v.Prev.offset < minOff { + v.Prev.offset = 0 + } else { + v.Prev.offset = v.Prev.offset - e.cur + maxMatchOffset + } + } + e.bTable[i] = v + } + e.cur = maxMatchOffset + } + + s := e.addBlock(src) + + // This check isn't in the Snappy implementation, but there, the caller + // instead of the callee handles this case. + if len(src) < minNonLiteralBlockSize { + // We do not fill the token table. + // This will be picked up by caller. + dst.n = uint16(len(src)) + return + } + + // Override src + src = e.hist + nextEmit := s + + // sLimit is when to stop looking for offset/length copies. The inputMargin + // lets us use a fast path for emitLiteral in the main loop, while we are + // looking for copies. + sLimit := int32(len(src) - inputMargin) + + // nextEmit is where in src the next emitLiteral should start from. + cv := load6432(src, s) + nextHashS := hash4x64(cv, tableBits) + nextHashL := hash7(cv, tableBits) + + for { + const skipLog = 6 + const doEvery = 1 + + nextS := s + var l int32 + var t int32 + for { + s = nextS + nextS = s + doEvery + (s-nextEmit)>>skipLog + if nextS > sLimit { + goto emitRemainder + } + // Fetch a short+long candidate + sCandidate := e.table[nextHashS&tableMask] + lCandidate := e.bTable[nextHashL&tableMask] + next := load6432(src, nextS) + entry := tableEntry{offset: s + e.cur, val: uint32(cv)} + e.table[nextHashS&tableMask] = entry + eLong := &e.bTable[nextHashL&tableMask] + eLong.Cur, eLong.Prev = entry, eLong.Cur + + nextHashS = hash4x64(next, tableBits) + nextHashL = hash7(next, tableBits) + + t = lCandidate.Cur.offset - e.cur + if s-t < maxMatchOffset { + if uint32(cv) == lCandidate.Cur.val { + // Store the next match + e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + eLong := &e.bTable[nextHashL&tableMask] + eLong.Cur, eLong.Prev = tableEntry{offset: nextS + e.cur, val: uint32(next)}, eLong.Cur + + t2 := lCandidate.Prev.offset - e.cur + if s-t2 < maxMatchOffset && uint32(cv) == lCandidate.Prev.val { + l = e.matchlen(s+4, t+4, src) + 4 + ml1 := e.matchlen(s+4, t2+4, src) + 4 + if ml1 > l { + t = t2 + l = ml1 + break + } + } + break + } + t = lCandidate.Prev.offset - e.cur + if s-t < maxMatchOffset && uint32(cv) == lCandidate.Prev.val { + // Store the next match + e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + eLong := &e.bTable[nextHashL&tableMask] + eLong.Cur, eLong.Prev = tableEntry{offset: nextS + e.cur, val: uint32(next)}, eLong.Cur + break + } + } + + t = sCandidate.offset - e.cur + if s-t < maxMatchOffset && uint32(cv) == sCandidate.val { + // Found a 4 match... + l = e.matchlen(s+4, t+4, src) + 4 + lCandidate = e.bTable[nextHashL&tableMask] + // Store the next match + + e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + eLong := &e.bTable[nextHashL&tableMask] + eLong.Cur, eLong.Prev = tableEntry{offset: nextS + e.cur, val: uint32(next)}, eLong.Cur + + // If the next long is a candidate, use that... + t2 := lCandidate.Cur.offset - e.cur + if nextS-t2 < maxMatchOffset { + if lCandidate.Cur.val == uint32(next) { + ml := e.matchlen(nextS+4, t2+4, src) + 4 + if ml > l { + t = t2 + s = nextS + l = ml + break + } + } + // If the previous long is a candidate, use that... + t2 = lCandidate.Prev.offset - e.cur + if nextS-t2 < maxMatchOffset && lCandidate.Prev.val == uint32(next) { + ml := e.matchlen(nextS+4, t2+4, src) + 4 + if ml > l { + t = t2 + s = nextS + l = ml + break + } + } + } + break + } + cv = next + } + + // A 4-byte match has been found. We'll later see if more than 4 bytes + // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit + // them as literal bytes. + + // Extend the 4-byte match as long as possible. + if l == 0 { + l = e.matchlen(s+4, t+4, src) + 4 + } + + // Extend backwards + tMin := s - maxMatchOffset + if tMin < 0 { + tMin = 0 + } + for t > tMin && s > nextEmit && src[t-1] == src[s-1] && l < maxMatchLength { + s-- + t-- + l++ + } + if nextEmit < s { + emitLiteral(dst, src[nextEmit:s]) + } + if false { + if t >= s { + panic(fmt.Sprintln("s-t", s, t)) + } + if l > maxMatchLength { + panic("mml") + } + if (s - t) > maxMatchOffset { + panic(fmt.Sprintln("mmo", s-t)) + } + if l < baseMatchLength { + panic("bml") + } + } + + // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) + dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) + dst.n++ + s += l + nextEmit = s + if nextS >= s { + s = nextS + 1 + } + if nextS >= s { + s = nextS + 1 + } + + if s >= sLimit { + // Index first pair after match end. + if false && int(s+8) < len(src) { + cv := load6432(src, s) + e.table[hash4x64(cv, tableBits)&tableMask] = tableEntry{offset: s + e.cur, val: uint32(cv)} + eLong := &e.bTable[hash7(cv, tableBits)&tableMask] + eLong.Cur, eLong.Prev = tableEntry{offset: s + e.cur, val: uint32(cv)}, eLong.Cur + } + goto emitRemainder + } + + // Store every 4th hash in-between + if true { + for i := s - l + 4; i < s-2; i += 4 { + cv := load6432(src, i) + t := tableEntry{offset: i + e.cur, val: uint32(cv)} + e.table[hash4x64(cv, tableBits)&tableMask] = t + eLong := &e.bTable[hash7(cv, tableBits)&tableMask] + eLong.Cur, eLong.Prev = t, eLong.Cur + } + } + + // We could immediately start working at s now, but to improve + // compression we first update the hash table at s-1 and at s. + x := load6432(src, s-1) + o := e.cur + s - 1 + prevHashS := hash4x64(x, tableBits) + prevHashL := hash7(x, tableBits) + e.table[prevHashS&tableMask] = tableEntry{offset: o, val: uint32(x)} + eLong := &e.bTable[prevHashL&tableMask] + eLong.Cur, eLong.Prev = tableEntry{offset: o, val: uint32(x)}, eLong.Cur + x >>= 8 + nextHashS = hash4x64(x, tableBits) + nextHashL = hash7(x, tableBits) + cv = x + } + +emitRemainder: + if int(nextEmit) < len(src) { + emitLiteral(dst, src[nextEmit:]) + } +} diff --git a/flate/level6.go b/flate/level6.go new file mode 100644 index 0000000000..a328bfbc2f --- /dev/null +++ b/flate/level6.go @@ -0,0 +1,270 @@ +package flate + +import "fmt" + +type fastEncL6 struct { + fastGen + table [tableSize]tableEntry + bTable [tableSize]tableEntryPrev +} + +func (e *fastEncL6) Encode(dst *tokens, src []byte) { + const ( + inputMargin = 12 - 1 + minNonLiteralBlockSize = 1 + 1 + inputMargin + ) + + // Protect against e.cur wraparound. + for e.cur >= (1<<31)-(maxStoreBlockSize*2) { + if len(e.hist) == 0 { + for i := range e.table[:] { + e.table[i] = tableEntry{} + } + for i := range e.bTable[:] { + e.bTable[i] = tableEntryPrev{} + } + e.cur = maxMatchOffset + break + } + // Shift down everything in the table that isn't already too far away. + minOff := e.cur + int32(len(e.hist)) - maxMatchOffset + for i := range e.table[:] { + v := e.table[i].offset + if v < minOff { + v = 0 + } else { + v = v - e.cur + maxMatchOffset + } + e.table[i].offset = v + } + for i := range e.bTable[:] { + v := e.bTable[i] + if v.Cur.offset < minOff { + v.Cur.offset = 0 + v.Prev.offset = 0 + } else { + v.Cur.offset = v.Cur.offset - e.cur + maxMatchOffset + if v.Prev.offset < minOff { + v.Prev.offset = 0 + } else { + v.Prev.offset = v.Prev.offset - e.cur + maxMatchOffset + } + } + e.bTable[i] = v + } + e.cur = maxMatchOffset + } + + s := e.addBlock(src) + + // This check isn't in the Snappy implementation, but there, the caller + // instead of the callee handles this case. + if len(src) < minNonLiteralBlockSize { + // We do not fill the token table. + // This will be picked up by caller. + dst.n = uint16(len(src)) + return + } + + // Override src + src = e.hist + nextEmit := s + + // sLimit is when to stop looking for offset/length copies. The inputMargin + // lets us use a fast path for emitLiteral in the main loop, while we are + // looking for copies. + sLimit := int32(len(src) - inputMargin) + + // nextEmit is where in src the next emitLiteral should start from. + cv := load6432(src, s) + nextHashS := hash4x64(cv, tableBits) + nextHashL := hash7(cv, tableBits) + repeat := int32(0) + + for { + const skipLog = 6 + const doEvery = 1 + + nextS := s + var l int32 + var t int32 + for { + s = nextS + nextS = s + doEvery + (s-nextEmit)>>skipLog + if nextS > sLimit { + goto emitRemainder + } + // Fetch a short+long candidate + sCandidate := e.table[nextHashS&tableMask] + lCandidate := e.bTable[nextHashL&tableMask] + next := load6432(src, nextS) + entry := tableEntry{offset: s + e.cur, val: uint32(cv)} + e.table[nextHashS&tableMask] = entry + eLong := &e.bTable[nextHashL&tableMask] + eLong.Cur, eLong.Prev = entry, eLong.Cur + + nextHashS = hash4x64(next, tableBits) + nextHashL = hash7(next, tableBits) + + t = lCandidate.Cur.offset - e.cur + if s-t < maxMatchOffset { + if uint32(cv) == lCandidate.Cur.val { + // Store the next match + e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + eLong := &e.bTable[nextHashL&tableMask] + eLong.Cur, eLong.Prev = tableEntry{offset: nextS + e.cur, val: uint32(next)}, eLong.Cur + + t2 := lCandidate.Prev.offset - e.cur + if s-t2 < maxMatchOffset && uint32(cv) == lCandidate.Prev.val { + l = e.matchlen(s+4, t+4, src) + 4 + ml1 := e.matchlen(s+4, t2+4, src) + 4 + if ml1 > l { + t = t2 + l = ml1 + break + } + } + break + } + t = lCandidate.Prev.offset - e.cur + if s-t < maxMatchOffset && uint32(cv) == lCandidate.Prev.val { + // Store the next match + e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + eLong := &e.bTable[nextHashL&tableMask] + eLong.Cur, eLong.Prev = tableEntry{offset: nextS + e.cur, val: uint32(next)}, eLong.Cur + break + } + } + + t = sCandidate.offset - e.cur + if s-t < maxMatchOffset && uint32(cv) == sCandidate.val { + // Found a 4 match... + l = e.matchlen(s+4, t+4, src) + 4 + lCandidate = e.bTable[nextHashL&tableMask] + + t2 := s - repeat + if repeat > 0 && load3232(src, t2) == uint32(cv) { + ml := e.matchlen(s+4, t2+4, src) + 4 + if ml > l { + t = t2 + l = ml + } + } + // Store the next match + e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + eLong := &e.bTable[nextHashL&tableMask] + eLong.Cur, eLong.Prev = tableEntry{offset: nextS + e.cur, val: uint32(next)}, eLong.Cur + + // If the next long is a candidate, use that... + t2 = lCandidate.Cur.offset - e.cur + if nextS-t2 < maxMatchOffset { + if lCandidate.Cur.val == uint32(next) { + ml := e.matchlen(nextS+4, t2+4, src) + 4 + if ml > l { + t = t2 + s = nextS + l = ml + } + } + // If the previous long is a candidate, use that... + t2 = lCandidate.Prev.offset - e.cur + if nextS-t2 < maxMatchOffset && lCandidate.Prev.val == uint32(next) { + ml := e.matchlen(nextS+4, t2+4, src) + 4 + if ml > l { + t = t2 + s = nextS + l = ml + break + } + } + } + break + } + cv = next + } + + // A 4-byte match has been found. We'll later see if more than 4 bytes + // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit + // them as literal bytes. + + // Extend the 4-byte match as long as possible. + if l == 0 { + l = e.matchlen(s+4, t+4, src) + 4 + } + + // Extend backwards + tMin := s - maxMatchOffset + if tMin < 0 { + tMin = 0 + } + for t > tMin && s > nextEmit && src[t-1] == src[s-1] && l < maxMatchLength { + s-- + t-- + l++ + } + if nextEmit < s { + emitLiteral(dst, src[nextEmit:s]) + } + if false { + if t >= s { + panic(fmt.Sprintln("s-t", s, t)) + } + if l > maxMatchLength { + panic("mml") + } + if (s - t) > maxMatchOffset { + panic(fmt.Sprintln("mmo", s-t)) + } + if l < baseMatchLength { + panic("bml") + } + } + + // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) + dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) + dst.n++ + repeat = s - t + s += l + nextEmit = s + if nextS >= s { + s = nextS + 1 + } + if nextS >= s { + s = nextS + 1 + } + + if s >= sLimit { + // Index first pair after match end. + for i := nextS + 1; i < int32(len(src))-8; i += 2 { + cv := load6432(src, i) + e.table[hash4x64(cv, tableBits)&tableMask] = tableEntry{offset: i + e.cur, val: uint32(cv)} + eLong := &e.bTable[hash7(cv, tableBits)&tableMask] + eLong.Cur, eLong.Prev = tableEntry{offset: i + e.cur, val: uint32(cv)}, eLong.Cur + } + goto emitRemainder + } + + // Store every 2nd hash in-between + if true { + for i := nextS + 1; i < s; i++ { + cv := load6432(src, i) + t := tableEntry{offset: i + e.cur, val: uint32(cv)} + e.table[hash4x64(cv, tableBits)&tableMask] = t + eLong := &e.bTable[hash7(cv, tableBits)&tableMask] + eLong.Cur, eLong.Prev = t, eLong.Cur + } + } + + // We could immediately start working at s now, but to improve + // compression we first update the hash table at s-1 and at s. + x := load6432(src, s) + nextHashS = hash4x64(x, tableBits) + nextHashL = hash7(x, tableBits) + cv = x + } + +emitRemainder: + if int(nextEmit) < len(src) { + emitLiteral(dst, src[nextEmit:]) + } +} diff --git a/flate/testdata/regression.zip b/flate/testdata/regression.zip new file mode 100644 index 0000000000000000000000000000000000000000..6307c62c3841ae8135ef2bb0505e058def4953d4 GIT binary patch literal 1879 zcma)-doiU{jBE+3DnYt zfgm6~kmzhrunKK_>pL(A1knP496=xujgH3R00tgIKvS7m28N0yu&8Jnk%l6=xd8+$ zKxJX6SR!qrR2nQt!E#OZ`a`*1Ao*^QI^3t`JS-;gb{;P}mso-qUIZu;qqb}ag~^%_ zdM%CUT3e^^`Igt71}R%nndcbLAd>jFN0YX7(O||Ynv1<2b1Vm8XD0eUzlSJLDl%V{ zI^_jdQ7$#M!YiF>|IONWds5uj7@7FjU{L}8ZG1UjL)?cpnPIM8-@AN&+Ip#geO#nc zM6(K3Ya+{9`lAve0pnx5p&63#9zAkcF`?SkKh-$XR1}huJBoZ0EnSJ-eyUkgnB3A( z)%>^RZug@xv#D=gjyHrnZ%P(DdYNTb)RSSUH!0KwfNJsW?H=L!-uf* zgAVuc`>z^OE%jD3f@yK#U=Q05bM|Gr#@~g7SfgiV}*?2YiZy4oO&Y?2X2>I={4iw68aKN$Se!j*nb$fkc-g;m#;|n_(`m5s%tBmt*OZ63*#= ztUd`_0F`)@!`V1Lr;=R>2Y%@?feVy7Qw)Z4|030QL5SJBL$b#Tj)RuR6x7y;k-JMrk;DRmDQvlJ#hi%yfGkNwsP3K?wA?aakYJOtuFhgdu=zl zRY{BH*-ybm2t>4Mj`kYq&-vkC@%F~4cO!?47OgtB@1NSKo*sy(tovtI0CE$)Z_Hv- zMgBauH*VV?#fYz4@Zbm$RhH~ynDG>wI`5kMzLTE;NRj6WkY|yu0iiZfmah`!8(+N7 z;~}4+T-<#xQww4_@3l7vM$z0cHq?CpHF7@?c-2%eo{~=+lYmuiqSmyfLtWIyGc~}7 z-DjQ9YZYg7dPqK)=@_eVD(qMfH*>+v{cp|T?kR67FlH9F~ zdQZp_o#LvZ`Y!h+I=J?DAMy8peA|BPd-kx^{K&5_+8zXD%R5S%^H&Pco35{Wmcx?| zOA22!Go)rgdQ1Br1}<5DB`~hOCs>8F#0EeNY00N6nCxvuzYrJ8S#)GC2@2m%PZ#g=4GCQ0Ox`Wq0Z}ruN*>;)^TKs3n z{X04K7Y@&wt4CWFKmTZb`iCM6bXj!dmCreSM0V%Wx?ad62#kkk+w6yTz{2bl#~1vk z97O4*P@xg}Wm1%^d?oQwG9v^0-di`1!8_1De(#Div;Rh#9oYBH#c*#O%l6TW{ViBt zgPp2GbSBFZ+q99i!Awg}5)`m7XXjxbl5NnQwO*PjB+R;r+d}*1Bg^WWIX{D5I>&Kn ze3_i!tCei9Vo;3xI3rBf?7j>2gy+pxc(1&BQYHJyVV%4Zv@K65*@<6F5KMeZD0O&v z+2wK=iMo|d4lM)fruRs9_chKB)y|EiS0Bxn@0!gm+P1|cGG#{+i}y-(v+^Q9~~*Y>CN442a1;H{C5Sj}o@hyg%uDe`eCJ1-VXs(;|#0 z=+3z+i!HtuV}>c4shxP?;dDKNl3tjP2yPs+M`U`9v-AzgGVw&_!jP9oxc=7MobU>S zDs2a{dxObTEKq&&!CCW~8^NEWr5K+yb#d=u8QDVP6%q)Bz(D^$K-My3?ZJQhzdcU= xsQPC{_&>QXszqyXeyc71QT5N@{;F!M{bkp0Lp>x=2l{1FYpoz_+j#xf{TInP71RI# literal 0 HcmV?d00001 From b34654ee54d003760ecae4760270e2b24c409023 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Wed, 12 Jun 2019 21:53:48 +0200 Subject: [PATCH 23/54] Support dictionaries for levels 1-6. --- flate/deflate.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/flate/deflate.go b/flate/deflate.go index 59fb272972..2d4bd5df84 100644 --- a/flate/deflate.go +++ b/flate/deflate.go @@ -208,8 +208,16 @@ func (d *compressor) writeBlockSkip(tok tokens, index int, eof bool) error { func (d *compressor) fillWindow(b []byte) { // Do not fill window if we are in store-only mode, // use constant or Snappy compression. - switch d.compressionLevel.level { - case 0, 1, 2: + if d.level == 0 { + return + } + if d.snap != nil { + // encode the last data, but discard the result + if len(b) > maxMatchOffset { + b = b[len(b)-maxMatchOffset:] + } + d.snap.Encode(&d.tokens, b) + d.tokens.n = 0 return } s := d.state From 05d211c6e983ba847d464ebf844dec0091a19760 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Thu, 13 Jun 2019 22:35:24 +0200 Subject: [PATCH 24/54] Do token indexing as they are added. --- flate/asm_test.go | 6 +- flate/crc32_amd64.go | 6 -- flate/crc32_amd64.s | 62 --------------- flate/crc32_noasm.go | 10 --- flate/deflate.go | 130 ++++++++++++++----------------- flate/fast_encoder.go | 23 ++++-- flate/huffman_bit_writer.go | 91 ++++++++++++---------- flate/huffman_bit_writer_test.go | 24 +++--- flate/huffman_code.go | 32 +++++--- flate/level1.go | 3 +- flate/level2.go | 4 +- flate/level3.go | 4 +- flate/level4.go | 4 +- flate/level5.go | 4 +- flate/level6.go | 27 ++++--- flate/testdata/regression.zip | Bin 1879 -> 12361 bytes flate/token.go | 112 ++++++++++++++++++++++++-- 17 files changed, 286 insertions(+), 256 deletions(-) diff --git a/flate/asm_test.go b/flate/asm_test.go index 40bf210f5f..0a04f2376d 100644 --- a/flate/asm_test.go +++ b/flate/asm_test.go @@ -168,8 +168,8 @@ func TestHistogram(t *testing.T) { for i := 0; i <= maxLen; i += is { // Test different offsets for j := 0; j < maxOff; j += js { - var got [256]int32 - var reference [256]int32 + var got [256]uint16 + var reference [256]uint16 histogram(a[j:i+j], got[:]) histogramReference(a[j:i+j], reference[:]) @@ -183,7 +183,7 @@ func TestHistogram(t *testing.T) { } // histogramReference is a reference -func histogramReference(b []byte, h []int32) { +func histogramReference(b []byte, h []uint16) { if len(h) < 256 { panic("Histogram too small") } diff --git a/flate/crc32_amd64.go b/flate/crc32_amd64.go index 8298d309ae..2b555e7745 100644 --- a/flate/crc32_amd64.go +++ b/flate/crc32_amd64.go @@ -30,12 +30,6 @@ func crc32sseAll(a []byte, dst []uint32) //go:noescape func matchLenSSE4(a, b []byte, max int) int -// histogram accumulates a histogram of b in h. -// h must be at least 256 entries in length, -// and must be cleared before calling this function. -//go:noescape -func histogram(b []byte, h []int32) - // Detect SSE 4.2 feature. func init() { useSSE42 = cpuid.CPU.SSE42() diff --git a/flate/crc32_amd64.s b/flate/crc32_amd64.s index a799437270..666542c914 100644 --- a/flate/crc32_amd64.s +++ b/flate/crc32_amd64.s @@ -150,65 +150,3 @@ matchLenEnd: SUBQ DX, DI MOVQ DI, ret+56(FP) RET - -// func histogram(b []byte, h []int32) -TEXT ·histogram(SB), 4, $0 - MOVQ b+0(FP), SI // SI: &b - MOVQ b_len+8(FP), R9 // R9: len(b) - MOVQ h+24(FP), DI // DI: Histogram - MOVQ R9, R8 - SHRQ $3, R8 - JZ hist1 - XORQ R11, R11 - -loop_hist8: - MOVQ (SI), R10 - - MOVB R10, R11 - INCL (DI)(R11*4) - SHRQ $8, R10 - - MOVB R10, R11 - INCL (DI)(R11*4) - SHRQ $8, R10 - - MOVB R10, R11 - INCL (DI)(R11*4) - SHRQ $8, R10 - - MOVB R10, R11 - INCL (DI)(R11*4) - SHRQ $8, R10 - - MOVB R10, R11 - INCL (DI)(R11*4) - SHRQ $8, R10 - - MOVB R10, R11 - INCL (DI)(R11*4) - SHRQ $8, R10 - - MOVB R10, R11 - INCL (DI)(R11*4) - SHRQ $8, R10 - - INCL (DI)(R10*4) - - ADDQ $8, SI - DECQ R8 - JNZ loop_hist8 - -hist1: - ANDQ $7, R9 - JZ end_hist - XORQ R10, R10 - -loop_hist1: - MOVB (SI), R10 - INCL (DI)(R10*4) - INCQ SI - DECQ R9 - JNZ loop_hist1 - -end_hist: - RET diff --git a/flate/crc32_noasm.go b/flate/crc32_noasm.go index dcf43bd50a..36dc8dde5c 100644 --- a/flate/crc32_noasm.go +++ b/flate/crc32_noasm.go @@ -23,13 +23,3 @@ func matchLenSSE4(a, b []byte, max int) int { panic("no assembler") return 0 } - -// histogram accumulates a histogram of b in h. -// -// len(h) must be >= 256, and h's elements must be all zeroes. -func histogram(b []byte, h []int32) { - h = h[:256] - for _, t := range b { - h[t]++ - } -} diff --git a/flate/deflate.go b/flate/deflate.go index 2d4bd5df84..58d84b3975 100644 --- a/flate/deflate.go +++ b/flate/deflate.go @@ -164,14 +164,14 @@ func (d *compressor) fillDeflate(b []byte) int { return n } -func (d *compressor) writeBlock(tok tokens, index int, eof bool) error { +func (d *compressor) writeBlock(tok *tokens, index int, eof bool) error { if index > 0 || eof { var window []byte if d.blockStart <= index { window = d.window[d.blockStart:index] } d.blockStart = index - d.w.writeBlock(tok.tokens[:tok.n], eof, window) + d.w.writeBlock(tok, eof, window) return d.w.err } return nil @@ -180,7 +180,7 @@ func (d *compressor) writeBlock(tok tokens, index int, eof bool) error { // writeBlockSkip writes the current block and uses the number of tokens // to determine if the block should be stored on no matches, or // only huffman encoded. -func (d *compressor) writeBlockSkip(tok tokens, index int, eof bool) error { +func (d *compressor) writeBlockSkip(tok *tokens, index int, eof bool) error { if index > 0 || eof { if d.blockStart <= index { window := d.window[d.blockStart:index] @@ -190,10 +190,10 @@ func (d *compressor) writeBlockSkip(tok tokens, index int, eof bool) error { d.w.writeBlockHuff(eof, window) } else { // Write a dynamic huffman block. - d.w.writeBlockDynamic(tok.tokens[:tok.n], eof, window) + d.w.writeBlockDynamic(tok, eof, window) } } else { - d.w.writeBlock(tok.tokens[:tok.n], eof, nil) + d.w.writeBlock(tok, eof, nil) } d.blockStart = index return d.w.err @@ -217,7 +217,7 @@ func (d *compressor) fillWindow(b []byte) { b = b[len(b)-maxMatchOffset:] } d.snap.Encode(&d.tokens, b) - d.tokens.n = 0 + d.tokens.Reset() return } s := d.state @@ -456,10 +456,10 @@ func (d *compressor) deflate() { } if lookahead == 0 { if d.tokens.n > 0 { - if d.err = d.writeBlockSkip(d.tokens, s.index, false); d.err != nil { + if d.err = d.writeBlockSkip(&d.tokens, s.index, false); d.err != nil { return } - d.tokens.n = 0 + d.tokens.Reset() } return } @@ -490,8 +490,7 @@ func (d *compressor) deflate() { // There was a match at the previous step, and the current match is // not better. Output the previous match. // "s.length-3" should NOT be "s.length-minMatchLength", since the format always assume 3 - d.tokens.tokens[d.tokens.n] = matchToken(uint32(s.length-3), uint32(s.offset-minOffsetSize)) - d.tokens.n++ + d.tokens.AddMatch(uint32(s.length-3), uint32(s.offset-minOffsetSize)) // Insert in the hash table all strings up to the end of the match. // index and index-1 are already inserted. If there is not enough // lookahead, the last two strings are not inserted into the hash @@ -537,10 +536,10 @@ func (d *compressor) deflate() { } if d.tokens.n == maxFlateBlockTokens { // The block includes the current character - if d.err = d.writeBlockSkip(d.tokens, s.index, false); d.err != nil { + if d.err = d.writeBlockSkip(&d.tokens, s.index, false); d.err != nil { return } - d.tokens.n = 0 + d.tokens.Reset() } } else { s.ii++ @@ -549,13 +548,12 @@ func (d *compressor) deflate() { end = d.windowEnd } for i := s.index; i < end; i++ { - d.tokens.tokens[d.tokens.n] = literalToken(uint32(d.window[i])) - d.tokens.n++ + d.tokens.AddLiteral(d.window[i]) if d.tokens.n == maxFlateBlockTokens { - if d.err = d.writeBlockSkip(d.tokens, i+1, false); d.err != nil { + if d.err = d.writeBlockSkip(&d.tokens, i+1, false); d.err != nil { return } - d.tokens.n = 0 + d.tokens.Reset() } } s.index = end @@ -597,15 +595,14 @@ func (d *compressor) deflateLazy() { // Flush current output block if any. if d.byteAvailable { // There is still one pending token that needs to be flushed - d.tokens.tokens[d.tokens.n] = literalToken(uint32(d.window[s.index-1])) - d.tokens.n++ + d.tokens.AddLiteral(d.window[s.index-1]) d.byteAvailable = false } if d.tokens.n > 0 { - if d.err = d.writeBlock(d.tokens, s.index, false); d.err != nil { + if d.err = d.writeBlock(&d.tokens, s.index, false); d.err != nil { return } - d.tokens.n = 0 + d.tokens.Reset() } return } @@ -636,8 +633,7 @@ func (d *compressor) deflateLazy() { if prevLength >= minMatchLength && s.length <= prevLength { // There was a match at the previous step, and the current match is // not better. Output the previous match. - d.tokens.tokens[d.tokens.n] = matchToken(uint32(prevLength-3), uint32(prevOffset-minOffsetSize)) - d.tokens.n++ + d.tokens.AddMatch(uint32(prevLength-3), uint32(prevOffset-minOffsetSize)) // Insert in the hash table all strings up to the end of the match. // index and index-1 are already inserted. If there is not enough @@ -678,10 +674,10 @@ func (d *compressor) deflateLazy() { s.length = minMatchLength - 1 if d.tokens.n == maxFlateBlockTokens { // The block includes the current character - if d.err = d.writeBlock(d.tokens, s.index, false); d.err != nil { + if d.err = d.writeBlock(&d.tokens, s.index, false); d.err != nil { return } - d.tokens.n = 0 + d.tokens.Reset() } } else { // Reset, if we got a match this run. @@ -691,13 +687,12 @@ func (d *compressor) deflateLazy() { // We have a byte waiting. Emit it. if d.byteAvailable { s.ii++ - d.tokens.tokens[d.tokens.n] = literalToken(uint32(d.window[s.index-1])) - d.tokens.n++ + d.tokens.AddLiteral(d.window[s.index-1]) if d.tokens.n == maxFlateBlockTokens { - if d.err = d.writeBlock(d.tokens, s.index, false); d.err != nil { + if d.err = d.writeBlock(&d.tokens, s.index, false); d.err != nil { return } - d.tokens.n = 0 + d.tokens.Reset() } s.index++ @@ -710,26 +705,24 @@ func (d *compressor) deflateLazy() { break } - d.tokens.tokens[d.tokens.n] = literalToken(uint32(d.window[s.index-1])) - d.tokens.n++ + d.tokens.AddLiteral(d.window[s.index-1]) if d.tokens.n == maxFlateBlockTokens { - if d.err = d.writeBlock(d.tokens, s.index, false); d.err != nil { + if d.err = d.writeBlock(&d.tokens, s.index, false); d.err != nil { return } - d.tokens.n = 0 + d.tokens.Reset() } s.index++ } // Flush last byte - d.tokens.tokens[d.tokens.n] = literalToken(uint32(d.window[s.index-1])) - d.tokens.n++ + d.tokens.AddLiteral(d.window[s.index-1]) d.byteAvailable = false // s.length = minMatchLength - 1 // not needed, since s.ii is reset above, so it should never be > minMatchLength if d.tokens.n == maxFlateBlockTokens { - if d.err = d.writeBlock(d.tokens, s.index, false); d.err != nil { + if d.err = d.writeBlock(&d.tokens, s.index, false); d.err != nil { return } - d.tokens.n = 0 + d.tokens.Reset() } } } else { @@ -772,10 +765,10 @@ func (d *compressor) deflateSSE() { } if lookahead == 0 { if d.tokens.n > 0 { - if d.err = d.writeBlockSkip(d.tokens, s.index, false); d.err != nil { + if d.err = d.writeBlockSkip(&d.tokens, s.index, false); d.err != nil { return } - d.tokens.n = 0 + d.tokens.Reset() } return } @@ -806,8 +799,7 @@ func (d *compressor) deflateSSE() { // There was a match at the previous step, and the current match is // not better. Output the previous match. // "s.length-3" should NOT be "s.length-minMatchLength", since the format always assume 3 - d.tokens.tokens[d.tokens.n] = matchToken(uint32(s.length-3), uint32(s.offset-minOffsetSize)) - d.tokens.n++ + d.tokens.AddMatch(uint32(s.length-3), uint32(s.offset-minOffsetSize)) // Insert in the hash table all strings up to the end of the match. // index and index-1 are already inserted. If there is not enough // lookahead, the last two strings are not inserted into the hash @@ -854,10 +846,10 @@ func (d *compressor) deflateSSE() { } if d.tokens.n == maxFlateBlockTokens { // The block includes the current character - if d.err = d.writeBlockSkip(d.tokens, s.index, false); d.err != nil { + if d.err = d.writeBlockSkip(&d.tokens, s.index, false); d.err != nil { return } - d.tokens.n = 0 + d.tokens.Reset() } } else { s.ii++ @@ -866,13 +858,12 @@ func (d *compressor) deflateSSE() { end = d.windowEnd } for i := s.index; i < end; i++ { - d.tokens.tokens[d.tokens.n] = literalToken(uint32(d.window[i])) - d.tokens.n++ + d.tokens.AddLiteral(d.window[i]) if d.tokens.n == maxFlateBlockTokens { - if d.err = d.writeBlockSkip(d.tokens, i+1, false); d.err != nil { + if d.err = d.writeBlockSkip(&d.tokens, i+1, false); d.err != nil { return } - d.tokens.n = 0 + d.tokens.Reset() } } s.index = end @@ -914,15 +905,14 @@ func (d *compressor) deflateLazySSE() { // Flush current output block if any. if d.byteAvailable { // There is still one pending token that needs to be flushed - d.tokens.tokens[d.tokens.n] = literalToken(uint32(d.window[s.index-1])) - d.tokens.n++ + d.tokens.AddLiteral(d.window[s.index-1]) d.byteAvailable = false } if d.tokens.n > 0 { - if d.err = d.writeBlock(d.tokens, s.index, false); d.err != nil { + if d.err = d.writeBlock(&d.tokens, s.index, false); d.err != nil { return } - d.tokens.n = 0 + d.tokens.Reset() } return } @@ -953,8 +943,7 @@ func (d *compressor) deflateLazySSE() { if prevLength >= minMatchLength && s.length <= prevLength { // There was a match at the previous step, and the current match is // not better. Output the previous match. - d.tokens.tokens[d.tokens.n] = matchToken(uint32(prevLength-3), uint32(prevOffset-minOffsetSize)) - d.tokens.n++ + d.tokens.AddMatch(uint32(prevLength-3), uint32(prevOffset-minOffsetSize)) // Insert in the hash table all strings up to the end of the match. // index and index-1 are already inserted. If there is not enough @@ -995,10 +984,10 @@ func (d *compressor) deflateLazySSE() { s.length = minMatchLength - 1 if d.tokens.n == maxFlateBlockTokens { // The block includes the current character - if d.err = d.writeBlock(d.tokens, s.index, false); d.err != nil { + if d.err = d.writeBlock(&d.tokens, s.index, false); d.err != nil { return } - d.tokens.n = 0 + d.tokens.Reset() } } else { // Reset, if we got a match this run. @@ -1008,13 +997,12 @@ func (d *compressor) deflateLazySSE() { // We have a byte waiting. Emit it. if d.byteAvailable { s.ii++ - d.tokens.tokens[d.tokens.n] = literalToken(uint32(d.window[s.index-1])) - d.tokens.n++ + d.tokens.AddLiteral(d.window[s.index-1]) if d.tokens.n == maxFlateBlockTokens { - if d.err = d.writeBlock(d.tokens, s.index, false); d.err != nil { + if d.err = d.writeBlock(&d.tokens, s.index, false); d.err != nil { return } - d.tokens.n = 0 + d.tokens.Reset() } s.index++ @@ -1027,26 +1015,24 @@ func (d *compressor) deflateLazySSE() { break } - d.tokens.tokens[d.tokens.n] = literalToken(uint32(d.window[s.index-1])) - d.tokens.n++ + d.tokens.AddLiteral(d.window[s.index-1]) if d.tokens.n == maxFlateBlockTokens { - if d.err = d.writeBlock(d.tokens, s.index, false); d.err != nil { + if d.err = d.writeBlock(&d.tokens, s.index, false); d.err != nil { return } - d.tokens.n = 0 + d.tokens.Reset() } s.index++ } // Flush last byte - d.tokens.tokens[d.tokens.n] = literalToken(uint32(d.window[s.index-1])) - d.tokens.n++ + d.tokens.AddLiteral(d.window[s.index-1]) d.byteAvailable = false // s.length = minMatchLength - 1 // not needed, since s.ii is reset above, so it should never be > minMatchLength if d.tokens.n == maxFlateBlockTokens { - if d.err = d.writeBlock(d.tokens, s.index, false); d.err != nil { + if d.err = d.writeBlock(&d.tokens, s.index, false); d.err != nil { return } - d.tokens.n = 0 + d.tokens.Reset() } } } else { @@ -1100,13 +1086,13 @@ func (d *compressor) storeFast() { } if d.windowEnd <= 32 { d.err = d.writeStoredBlock(d.window[:d.windowEnd]) - d.tokens.n = 0 + d.tokens.Reset() d.windowEnd = 0 } else { d.w.writeBlockHuff(false, d.window[:d.windowEnd]) d.err = d.w.err } - d.tokens.n = 0 + d.tokens.Reset() d.windowEnd = 0 d.snap.Reset() return @@ -1122,10 +1108,10 @@ func (d *compressor) storeFast() { d.w.writeBlockHuff(false, d.window[:d.windowEnd]) d.err = d.w.err } else { - d.w.writeBlockDynamic(d.tokens.tokens[:d.tokens.n], false, d.window[:d.windowEnd]) + d.w.writeBlockDynamic(&d.tokens, false, d.window[:d.windowEnd]) d.err = d.w.err } - d.tokens.n = 0 + d.tokens.Reset() d.windowEnd = 0 } @@ -1215,7 +1201,7 @@ func (d *compressor) reset(w io.Writer) { if d.snap != nil { d.snap.Reset() d.windowEnd = 0 - d.tokens.n = 0 + d.tokens.Reset() return } switch d.compressionLevel.chain { @@ -1234,7 +1220,7 @@ func (d *compressor) reset(w io.Writer) { s.hashOffset = 1 s.index, d.windowEnd = 0, 0 d.blockStart, d.byteAvailable = 0, false - d.tokens.n = 0 + d.tokens.Reset() s.length = minMatchLength - 1 s.offset = 0 s.hash = 0 diff --git a/flate/fast_encoder.go b/flate/fast_encoder.go index 3d2696facb..1178d375e1 100644 --- a/flate/fast_encoder.go +++ b/flate/fast_encoder.go @@ -5,23 +5,21 @@ package flate -import "math/bits" +import ( + "fmt" + "math/bits" +) // emitLiteral writes a literal chunk and returns the number of bytes written. func emitLiteral(dst *tokens, lit []byte) { ol := int(dst.n) for i, v := range lit { dst.tokens[(i+ol)&maxStoreBlockSize] = token(v) + dst.litHist[v]++ } dst.n += uint16(len(lit)) } -// emitCopy writes a copy chunk and returns the number of bytes written. -func emitCopy(dst *tokens, offset, length int) { - dst.tokens[dst.n] = matchToken(uint32(length-3), uint32(offset-minOffsetSize)) - dst.n++ -} - type fastEnc interface { Encode(dst *tokens, src []byte) Reset() @@ -184,6 +182,17 @@ type fastEncL4 struct { // The maximum length returned is maxMatchLength - 4. // It is assumed that s > t, that t >=0 and s < len(src). func (e *fastGen) matchlen(s, t int32, src []byte) int32 { + if false { + if t >= s { + panic(fmt.Sprint("t>=s", t, s)) + } + if int(s) >= len(src) { + panic(fmt.Sprint("s >= len(src)", s, len(src))) + } + if t < 0 { + panic(fmt.Sprint("t < 0 ", t)) + } + } s1 := int(s) + maxMatchLength - 4 if s1 > len(src) { s1 = len(src) diff --git a/flate/huffman_bit_writer.go b/flate/huffman_bit_writer.go index f46c654189..fbf30caf7a 100644 --- a/flate/huffman_bit_writer.go +++ b/flate/huffman_bit_writer.go @@ -87,10 +87,10 @@ type huffmanBitWriter struct { bits uint64 nbits uint bytes [256]byte - codegenFreq [codegenCodeCount]int32 + codegenFreq [codegenCodeCount]uint16 nbytes uint8 - literalFreq []int32 - offsetFreq []int32 + literalFreq []uint16 + offsetFreq []uint16 codegen []uint8 literalEncoding *huffmanEncoder offsetEncoding *huffmanEncoder @@ -101,8 +101,8 @@ type huffmanBitWriter struct { func newHuffmanBitWriter(w io.Writer) *huffmanBitWriter { return &huffmanBitWriter{ writer: w, - literalFreq: make([]int32, lengthCodesStart+32), - offsetFreq: make([]int32, 32), + literalFreq: make([]uint16, lengthCodesStart+32), + offsetFreq: make([]uint16, 32), codegen: make([]uint8, maxNumLit+offsetCodeCount+1), literalEncoding: newHuffmanEncoder(maxNumLit), codegenEncoding: newHuffmanEncoder(codegenCodeCount), @@ -116,6 +116,39 @@ func (w *huffmanBitWriter) reset(writer io.Writer) { w.bytes = [256]byte{} } +func (w *huffmanBitWriter) canReuse(t *tokens) (offsets, lits bool) { + offsets, lits = true, true + a := t.offHist[:offsetCodeCount] + b := w.offsetFreq[:len(a)] + for i := range a { + if b[i] == 0 && a[i] != 0 { + offsets = false + break + } + } + + a = t.extraHist[:maxNumLit-256] + b = w.literalFreq[256:maxNumLit] + b = b[:len(a)] + for i := range a { + if b[i] == 0 && a[i] != 0 { + lits = false + break + } + } + if lits { + a = t.litHist[:] + b = w.literalFreq[:len(a)] + for i := range a { + if b[i] == 0 && a[i] != 0 { + lits = false + break + } + } + } + return +} + func (w *huffmanBitWriter) flush() { if w.err != nil { w.nbits = 0 @@ -439,12 +472,12 @@ func (w *huffmanBitWriter) writeFixedHeader(isEof bool) { // is larger than the original bytes, the data will be written as a // stored block. // If the input is nil, the tokens will always be Huffman encoded. -func (w *huffmanBitWriter) writeBlock(tokens []token, eof bool, input []byte) { +func (w *huffmanBitWriter) writeBlock(tokens *tokens, eof bool, input []byte) { if w.err != nil { return } - tokens = append(tokens, endBlockMarker) + tokens.AddEOB() numLiterals, numOffsets := w.indexTokens(tokens) var extraBits int @@ -500,7 +533,7 @@ func (w *huffmanBitWriter) writeBlock(tokens []token, eof bool, input []byte) { } // Write the tokens. - w.writeTokens(tokens, literalEncoding.codes, offsetEncoding.codes) + w.writeTokens(tokens.Slice(), literalEncoding.codes, offsetEncoding.codes) } // writeBlockDynamic encodes a block using a dynamic Huffman table. @@ -508,14 +541,13 @@ func (w *huffmanBitWriter) writeBlock(tokens []token, eof bool, input []byte) { // histogram distribution. // If input is supplied and the compression savings are below 1/16th of the // input size the block is stored. -func (w *huffmanBitWriter) writeBlockDynamic(tokens []token, eof bool, input []byte) { +func (w *huffmanBitWriter) writeBlockDynamic(tokens *tokens, eof bool, input []byte) { if w.err != nil { return } - tokens = append(tokens, endBlockMarker) + tokens.AddEOB() numLiterals, numOffsets := w.indexTokens(tokens) - // Generate codegen and codegenFrequencies, which indicates how to encode // the literalEncoding and the offsetEncoding. w.generateCodegen(numLiterals, numOffsets, w.literalEncoding, w.offsetEncoding) @@ -533,47 +565,22 @@ func (w *huffmanBitWriter) writeBlockDynamic(tokens []token, eof bool, input []b w.writeDynamicHeader(numLiterals, numOffsets, numCodegens, eof) // Write the tokens. - w.writeTokens(tokens, w.literalEncoding.codes, w.offsetEncoding.codes) + w.writeTokens(tokens.Slice(), w.literalEncoding.codes, w.offsetEncoding.codes) } // indexTokens indexes a slice of tokens, and updates // literalFreq and offsetFreq, and generates literalEncoding // and offsetEncoding. // The number of literal and offset tokens is returned. -func (w *huffmanBitWriter) indexTokens(tokens []token) (numLiterals, numOffsets int) { - for i := range w.literalFreq { - w.literalFreq[i] = 0 - } - for i := range w.offsetFreq { - w.offsetFreq[i] = 0 - } +func (w *huffmanBitWriter) indexTokens(t *tokens) (numLiterals, numOffsets int) { + copy(w.literalFreq, t.litHist[:]) + copy(w.literalFreq[256:], t.extraHist[:]) + copy(w.offsetFreq, t.offHist[:offsetCodeCount]) - if len(tokens) == 0 { + if t.n == 0 { return } - // Only last token should be endBlockMarker. - if tokens[len(tokens)-1] == endBlockMarker { - w.literalFreq[endBlockMarker]++ - tokens = tokens[:len(tokens)-1] - } - - // Create slices up to the next power of two to avoid bounds checks. - lits := w.literalFreq[:256] - offs := w.offsetFreq[:32] - lengths := w.literalFreq[lengthCodesStart:] - lengths = lengths[:32] - for _, t := range tokens { - if t < endBlockMarker { - lits[t.literal()]++ - continue - } - length := t.length() - offset := t.offset() - lengths[lengthCode(length)&31]++ - offs[offsetCode(offset)&31]++ - } - // get the number of literals numLiterals = len(w.literalFreq) for w.literalFreq[numLiterals-1] == 0 { diff --git a/flate/huffman_bit_writer_test.go b/flate/huffman_bit_writer_test.go index 882d3abec1..9102f77e47 100644 --- a/flate/huffman_bit_writer_test.go +++ b/flate/huffman_bit_writer_test.go @@ -183,6 +183,7 @@ func testBlock(t *testing.T, test huffTest, ttype string) { test.want = fmt.Sprintf(test.want, ttype) } test.wantNoInput = fmt.Sprintf(test.wantNoInput, ttype) + tokens := indexTokens(test.tokens) if *update { if test.input != "" { t.Logf("Updating %q", test.want) @@ -199,7 +200,7 @@ func testBlock(t *testing.T, test huffTest, ttype string) { } defer f.Close() bw := newHuffmanBitWriter(f) - writeToType(t, ttype, bw, test.tokens, input) + writeToType(t, ttype, bw, tokens, input) } t.Logf("Updating %q", test.wantNoInput) @@ -210,7 +211,7 @@ func testBlock(t *testing.T, test huffTest, ttype string) { } defer f.Close() bw := newHuffmanBitWriter(f) - writeToType(t, ttype, bw, test.tokens, nil) + writeToType(t, ttype, bw, tokens, nil) return } @@ -228,7 +229,7 @@ func testBlock(t *testing.T, test huffTest, ttype string) { } var buf bytes.Buffer bw := newHuffmanBitWriter(&buf) - writeToType(t, ttype, bw, test.tokens, input) + writeToType(t, ttype, bw, tokens, input) got := buf.Bytes() if !bytes.Equal(got, want) { @@ -242,7 +243,7 @@ func testBlock(t *testing.T, test huffTest, ttype string) { // Test if the writer produces the same output after reset. buf.Reset() bw.reset(&buf) - writeToType(t, ttype, bw, test.tokens, input) + writeToType(t, ttype, bw, tokens, input) bw.flush() got = buf.Bytes() if !bytes.Equal(got, want) { @@ -263,7 +264,7 @@ func testBlock(t *testing.T, test huffTest, ttype string) { } var buf bytes.Buffer bw := newHuffmanBitWriter(&buf) - writeToType(t, ttype, bw, test.tokens, nil) + writeToType(t, ttype, bw, tokens, nil) got := buf.Bytes() if !bytes.Equal(got, wantNI) { @@ -281,7 +282,7 @@ func testBlock(t *testing.T, test huffTest, ttype string) { // Test if the writer produces the same output after reset. buf.Reset() bw.reset(&buf) - writeToType(t, ttype, bw, test.tokens, nil) + writeToType(t, ttype, bw, tokens, nil) bw.flush() got = buf.Bytes() if !bytes.Equal(got, wantNI) { @@ -295,12 +296,12 @@ func testBlock(t *testing.T, test huffTest, ttype string) { testWriterEOF(t, "wb", test, false) } -func writeToType(t *testing.T, ttype string, bw *huffmanBitWriter, tok []token, input []byte) { +func writeToType(t *testing.T, ttype string, bw *huffmanBitWriter, tok tokens, input []byte) { switch ttype { case "wb": - bw.writeBlock(tok, false, input) + bw.writeBlock(&tok, false, input) case "dyn": - bw.writeBlockDynamic(tok, false, input) + bw.writeBlockDynamic(&tok, false, input) default: panic("unknown test type") } @@ -333,11 +334,12 @@ func testWriterEOF(t *testing.T, ttype string, test huffTest, useInput bool) { } var buf bytes.Buffer bw := newHuffmanBitWriter(&buf) + tokens := indexTokens(test.tokens) switch ttype { case "wb": - bw.writeBlock(test.tokens, true, input) + bw.writeBlock(&tokens, true, input) case "dyn": - bw.writeBlockDynamic(test.tokens, true, input) + bw.writeBlockDynamic(&tokens, true, input) case "huff": bw.writeBlockHuff(true, input) default: diff --git a/flate/huffman_code.go b/flate/huffman_code.go index f65f793361..d0b9db7a83 100644 --- a/flate/huffman_code.go +++ b/flate/huffman_code.go @@ -25,7 +25,7 @@ type huffmanEncoder struct { type literalNode struct { literal uint16 - freq int32 + freq uint16 } // A levelInfo describes the state of the constructed tree for a given depth. @@ -54,7 +54,7 @@ func (h *hcode) set(code uint16, length uint16) { h.code = code } -func maxNode() literalNode { return literalNode{math.MaxUint16, math.MaxInt32} } +func maxNode() literalNode { return literalNode{math.MaxUint16, math.MaxUint16} } func newHuffmanEncoder(size int) *huffmanEncoder { // Make capacity to next power of two. @@ -108,7 +108,7 @@ func generateFixedOffsetEncoding() *huffmanEncoder { var fixedLiteralEncoding *huffmanEncoder = generateFixedLiteralEncoding() var fixedOffsetEncoding *huffmanEncoder = generateFixedOffsetEncoding() -func (h *huffmanEncoder) bitLength(freq []int32) int { +func (h *huffmanEncoder) bitLength(freq []uint16) int { var total int for i, f := range freq { if f != 0 { @@ -163,13 +163,13 @@ func (h *huffmanEncoder) bitCounts(list []literalNode, maxBits int32) []int32 { // We initialize the levels as if we had already figured this out. levels[level] = levelInfo{ level: level, - lastFreq: list[1].freq, - nextCharFreq: list[2].freq, - nextPairFreq: list[0].freq + list[1].freq, + lastFreq: int32(list[1].freq), + nextCharFreq: int32(list[2].freq), + nextPairFreq: int32(list[0].freq) + int32(list[1].freq), } leafCounts[level][level] = 2 if level == 1 { - levels[level].nextPairFreq = math.MaxInt32 + levels[level].nextPairFreq = math.MaxUint16 } } @@ -179,13 +179,13 @@ func (h *huffmanEncoder) bitCounts(list []literalNode, maxBits int32) []int32 { level := maxBits for { l := &levels[level] - if l.nextPairFreq == math.MaxInt32 && l.nextCharFreq == math.MaxInt32 { + if l.nextPairFreq == math.MaxUint16 && l.nextCharFreq == math.MaxUint16 { // We've run out of both leafs and pairs. // End all calculations for this level. // To make sure we never come back to this level or any lower level, // set nextPairFreq impossibly large. l.needed = 0 - levels[level+1].nextPairFreq = math.MaxInt32 + levels[level+1].nextPairFreq = math.MaxUint16 level++ continue } @@ -197,7 +197,7 @@ func (h *huffmanEncoder) bitCounts(list []literalNode, maxBits int32) []int32 { l.lastFreq = l.nextCharFreq // Lower leafCounts are the same of the previous node. leafCounts[level][level] = n - l.nextCharFreq = list[n].freq + l.nextCharFreq = int32(list[n].freq) } else { // The next item on this row is a pair from the previous row. // nextPairFreq isn't valid until we generate two @@ -273,7 +273,7 @@ func (h *huffmanEncoder) assignEncodingAndSize(bitCount []int32, list []literalN // // freq An array of frequencies, in which frequency[i] gives the frequency of literal i. // maxBits The maximum number of bits to use for any literal. -func (h *huffmanEncoder) generate(freq []int32, maxBits int32) { +func (h *huffmanEncoder) generate(freq []uint16, maxBits int32) { if h.freqcache == nil { // Allocate a reusable buffer with the longest possible frequency table. // Possible lengths are codegenCodeCount, offsetCodeCount and maxNumLit. @@ -345,3 +345,13 @@ func (s byFreq) Less(i, j int) bool { } func (s byFreq) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// histogram accumulates a histogram of b in h. +// +// len(h) must be >= 256, and h's elements must be all zeroes. +func histogram(b []byte, h []uint16) { + h = h[:256] + for _, t := range b { + h[t]++ + } +} diff --git a/flate/level1.go b/flate/level1.go index f68b568a0d..9329269d0c 100644 --- a/flate/level1.go +++ b/flate/level1.go @@ -128,8 +128,7 @@ func (e *fastEncL1) Encode(dst *tokens, src []byte) { } // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) - dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) - dst.n++ + dst.AddMatch(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) s += l nextEmit = s if nextS >= s { diff --git a/flate/level2.go b/flate/level2.go index 92dda361f7..f32c066836 100644 --- a/flate/level2.go +++ b/flate/level2.go @@ -152,9 +152,7 @@ func (e *fastEncL2) Encode(dst *tokens, src []byte) { emitLiteral(dst, src[nextEmit:s]) } - // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) - dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) - dst.n++ + dst.AddMatch(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) s += l nextEmit = s if nextS >= s { diff --git a/flate/level3.go b/flate/level3.go index 36217d5377..c14d82a2b8 100644 --- a/flate/level3.go +++ b/flate/level3.go @@ -150,9 +150,7 @@ func (e *fastEncL3) Encode(dst *tokens, src []byte) { emitLiteral(dst, src[nextEmit:s]) } - // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) - dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) - dst.n++ + dst.AddMatch(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) s += l nextEmit = s if nextS >= s { diff --git a/flate/level4.go b/flate/level4.go index a3cd514150..d0a9d69e73 100644 --- a/flate/level4.go +++ b/flate/level4.go @@ -152,9 +152,7 @@ func (e *fastEncL4) Encode(dst *tokens, src []byte) { } } - // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) - dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) - dst.n++ + dst.AddMatch(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) s += l nextEmit = s if nextS >= s { diff --git a/flate/level5.go b/flate/level5.go index 0c25d2bfc8..2f7cd35478 100644 --- a/flate/level5.go +++ b/flate/level5.go @@ -212,9 +212,7 @@ func (e *fastEncL5) Encode(dst *tokens, src []byte) { } } - // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) - dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) - dst.n++ + dst.AddMatch(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) s += l nextEmit = s if nextS >= s { diff --git a/flate/level6.go b/flate/level6.go index a328bfbc2f..8f712f62fb 100644 --- a/flate/level6.go +++ b/flate/level6.go @@ -79,7 +79,8 @@ func (e *fastEncL6) Encode(dst *tokens, src []byte) { cv := load6432(src, s) nextHashS := hash4x64(cv, tableBits) nextHashL := hash7(cv, tableBits) - repeat := int32(0) + // Repeat MUST be > 1 and within rabge + repeat := int32(1) for { const skipLog = 6 @@ -142,18 +143,24 @@ func (e *fastEncL6) Encode(dst *tokens, src []byte) { l = e.matchlen(s+4, t+4, src) + 4 lCandidate = e.bTable[nextHashL&tableMask] - t2 := s - repeat - if repeat > 0 && load3232(src, t2) == uint32(cv) { - ml := e.matchlen(s+4, t2+4, src) + 4 + // Store the next match + e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} + eLong := &e.bTable[nextHashL&tableMask] + eLong.Cur, eLong.Prev = tableEntry{offset: nextS + e.cur, val: uint32(next)}, eLong.Cur + + // Check repeat at s + const repOff = 1 + t2 := s - repeat + repOff + if load3232(src, t2) == uint32(cv>>(9*repOff)) { + ml := e.matchlen(s+4+repOff, t2+4, src) + 4 if ml > l { t = t2 l = ml + s += repOff + // Not worth checking more. + break } } - // Store the next match - e.table[nextHashS&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(next)} - eLong := &e.bTable[nextHashL&tableMask] - eLong.Cur, eLong.Prev = tableEntry{offset: nextS + e.cur, val: uint32(next)}, eLong.Cur // If the next long is a candidate, use that... t2 = lCandidate.Cur.offset - e.cur @@ -220,9 +227,7 @@ func (e *fastEncL6) Encode(dst *tokens, src []byte) { } } - // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) - dst.tokens[dst.n] = matchToken(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) - dst.n++ + dst.AddMatch(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) repeat = s - t s += l nextEmit = s diff --git a/flate/testdata/regression.zip b/flate/testdata/regression.zip index 6307c62c3841ae8135ef2bb0505e058def4953d4..f732814264287218d8a4444d6c3fe5870aeab7d1 100644 GIT binary patch delta 10595 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 diff --git a/flate/token.go b/flate/token.go index 141299b973..2d66b4c20f 100644 --- a/flate/token.go +++ b/flate/token.go @@ -4,6 +4,8 @@ package flate +import "math" + const ( // 2 bits: type 0 = literal 1=EOF 2=Match 3=Unused // 8 bits: xlength = length - MIN_MATCH_LENGTH @@ -68,18 +70,114 @@ var offsetCodes = [256]uint32{ type token uint32 type tokens struct { - tokens [maxStoreBlockSize + 1]token - n uint16 // Must be able to contain maxStoreBlockSize + n uint16 // Must be able to contain maxStoreBlockSize + tokens [maxStoreBlockSize + 1]token + litHist [256]uint16 // codes 0->255 + extraHist [32]uint16 // codes 256->maxnumlit + offHist [32]uint16 // offset codes } -// Convert a literal into a literal token. -func literalToken(literal uint32) token { return token(literalType + literal) } +func (t *tokens) Reset() { + if t.n == 0 { + return + } + t.n = 0 + for i := range t.litHist[:] { + t.litHist[i] = 0 + } + for i := range t.extraHist[:] { + t.extraHist[i] = 0 + } + for i := range t.offHist[:] { + t.offHist[i] = 0 + } +} -// Convert a < xlength, xoffset > pair into a match token. -func matchToken(xlength uint32, xoffset uint32) token { - return token(matchType + xlength< 0 { + shannon += math.Log2(total/n) * n + } + } + for _, v := range t.extraHist[:maxNumLit-256] { + n := float64(v) + if n > 0 { + shannon += math.Log2(total/n) * n + } + } + for _, v := range t.offHist[:offsetCodeCount] { + n := float64(v) + if n > 0 { + shannon += math.Log2(total/n) * n + } + } + return int(math.Ceil(shannon)) +} + +func (t *tokens) AddMatch(xlength uint32, xoffset uint32) { + t.tokens[t.n] = token(matchType + uint32(xlength)< Date: Fri, 14 Jun 2019 20:29:22 +0200 Subject: [PATCH 25/54] Fix size estimation function. --- flate/fast_encoder.go | 10 ------ flate/huffman_bit_writer.go | 17 ++++++++-- flate/token.go | 65 ++++++++++++++++++++++++++++--------- 3 files changed, 64 insertions(+), 28 deletions(-) diff --git a/flate/fast_encoder.go b/flate/fast_encoder.go index 1178d375e1..676f5b2489 100644 --- a/flate/fast_encoder.go +++ b/flate/fast_encoder.go @@ -10,16 +10,6 @@ import ( "math/bits" ) -// emitLiteral writes a literal chunk and returns the number of bytes written. -func emitLiteral(dst *tokens, lit []byte) { - ol := int(dst.n) - for i, v := range lit { - dst.tokens[(i+ol)&maxStoreBlockSize] = token(v) - dst.litHist[v]++ - } - dst.n += uint16(len(lit)) -} - type fastEnc interface { Encode(dst *tokens, src []byte) Reset() diff --git a/flate/huffman_bit_writer.go b/flate/huffman_bit_writer.go index fbf30caf7a..2084b7d03d 100644 --- a/flate/huffman_bit_writer.go +++ b/flate/huffman_bit_writer.go @@ -340,10 +340,22 @@ func (w *huffmanBitWriter) dynamicSize(litEnc, offEnc *huffmanEncoder, extraBits litEnc.bitLength(w.literalFreq) + offEnc.bitLength(w.offsetFreq) + extraBits - return size, numCodegens } +// extraBitSize will return the number of bits that will be written +// as "extra" bits on matches. +func (w *huffmanBitWriter) extraBitSize() int { + total := 0 + for i, n := range w.literalFreq[257:maxNumLit] { + total += int(n) * int(lengthExtraBits[i&31]) + } + for i, n := range w.offsetFreq[:offsetCodeCount] { + total += int(n) * int(offsetExtraBits[i&31]) + } + return total +} + // fixedSize returns the size of dynamically encoded data in bits. func (w *huffmanBitWriter) fixedSize(extraBits int) int { return 3 + @@ -552,8 +564,9 @@ func (w *huffmanBitWriter) writeBlockDynamic(tokens *tokens, eof bool, input []b // the literalEncoding and the offsetEncoding. w.generateCodegen(numLiterals, numOffsets, w.literalEncoding, w.offsetEncoding) w.codegenEncoding.generate(w.codegenFreq[:], 7) - size, numCodegens := w.dynamicSize(w.literalEncoding, w.offsetEncoding, 0) + size, numCodegens := w.dynamicSize(w.literalEncoding, w.offsetEncoding, w.extraBitSize()) + //fmt.Println("dynamicSize:", size>>3, "calc:", tokens.EstimatedBits()>>3) // Store bytes, if we don't get a reasonable improvement. if ssize, storable := w.storedSize(input); storable && ssize < (size+size>>4) { w.writeStoredHeader(len(input), eof) diff --git a/flate/token.go b/flate/token.go index 2d66b4c20f..5746502ec2 100644 --- a/flate/token.go +++ b/flate/token.go @@ -4,7 +4,9 @@ package flate -import "math" +import ( + "math" +) const ( // 2 bits: type 0 = literal 1=EOF 2=Match 3=Unused @@ -70,6 +72,7 @@ var offsetCodes = [256]uint32{ type token uint32 type tokens struct { + nLits int n uint16 // Must be able to contain maxStoreBlockSize tokens [maxStoreBlockSize + 1]token litHist [256]uint16 // codes 0->255 @@ -82,6 +85,7 @@ func (t *tokens) Reset() { return } t.n = 0 + t.nLits = 0 for i := range t.litHist[:] { t.litHist[i] = 0 } @@ -128,40 +132,69 @@ func indexTokens(in []token) tokens { return t } +// emitLiteral writes a literal chunk and returns the number of bytes written. +func emitLiteral(dst *tokens, lit []byte) { + ol := int(dst.n) + for i, v := range lit { + dst.tokens[(i+ol)&maxStoreBlockSize] = token(v) + dst.litHist[v]++ + } + dst.n += uint16(len(lit)) + dst.nLits += len(lit) +} + func (t *tokens) AddLiteral(lit byte) { t.tokens[t.n] = token(lit) t.litHist[lit]++ t.n++ + t.nLits++ } +// EstimatedBits will return an minimum size estimated by an *optimal* +// compression of the block. +// The size of the block func (t *tokens) EstimatedBits() int { shannon := float64(0) - total := float64(t.n) - for _, v := range t.litHist[:] { - n := float64(v) - if n > 0 { - shannon += math.Log2(total/n) * n + bits := int(0) + nMatches := 0 + if t.nLits > 0 { + invTotal := 1.0 / float64(t.nLits) + for _, v := range t.litHist[:] { + if v > 0 { + n := float64(v) + shannon += math.Ceil(-math.Log2(n*invTotal) * n) + } } - } - for _, v := range t.extraHist[:maxNumLit-256] { - n := float64(v) - if n > 0 { - shannon += math.Log2(total/n) * n + // Just add 15 for EOB + shannon += 15 + for _, v := range t.extraHist[1 : maxNumLit-256] { + if v > 0 { + n := float64(v) + shannon += math.Ceil(-math.Log2(n*invTotal) * n) + bits += int(lengthExtraBits[v&31]) * int(v) + nMatches += int(v) + } } } - for _, v := range t.offHist[:offsetCodeCount] { - n := float64(v) - if n > 0 { - shannon += math.Log2(total/n) * n + if nMatches > 0 { + invTotal := 1.0 / float64(nMatches) + for _, v := range t.offHist[:offsetCodeCount] { + if v > 0 { + n := float64(v) + shannon += math.Ceil(-math.Log2(n*invTotal) * n) + bits += int(offsetExtraBits[v&31]) * int(n) + } } } - return int(math.Ceil(shannon)) + + return int(shannon) + bits } func (t *tokens) AddMatch(xlength uint32, xoffset uint32) { t.tokens[t.n] = token(matchType + uint32(xlength)< Date: Fri, 14 Jun 2019 21:52:34 +0200 Subject: [PATCH 26/54] Move bit flushing to separate function. At least 5-8 faster due to inlining. --- flate/huffman_bit_writer.go | 87 +++++++++++-------------------------- 1 file changed, 26 insertions(+), 61 deletions(-) diff --git a/flate/huffman_bit_writer.go b/flate/huffman_bit_writer.go index 2084b7d03d..ce4c31d675 100644 --- a/flate/huffman_bit_writer.go +++ b/flate/huffman_bit_writer.go @@ -181,26 +181,7 @@ func (w *huffmanBitWriter) writeBits(b int32, nb uint) { w.bits |= uint64(b) << w.nbits w.nbits += nb if w.nbits >= 48 { - bits := w.bits - w.bits >>= 48 - w.nbits -= 48 - n := w.nbytes - w.bytes[n] = byte(bits) - w.bytes[n+1] = byte(bits >> 8) - w.bytes[n+2] = byte(bits >> 16) - w.bytes[n+3] = byte(bits >> 24) - w.bytes[n+4] = byte(bits >> 32) - w.bytes[n+5] = byte(bits >> 40) - n += 6 - if n >= bufferFlushSize { - if w.err != nil { - n = 0 - return - } - w.write(w.bytes[:n]) - n = 0 - } - w.nbytes = n + w.writeOutBits() } } @@ -381,27 +362,32 @@ func (w *huffmanBitWriter) writeCode(c hcode) { w.bits |= uint64(c.code) << w.nbits w.nbits += uint(c.len) if w.nbits >= 48 { - bits := w.bits - w.bits >>= 48 - w.nbits -= 48 - n := w.nbytes - w.bytes[n] = byte(bits) - w.bytes[n+1] = byte(bits >> 8) - w.bytes[n+2] = byte(bits >> 16) - w.bytes[n+3] = byte(bits >> 24) - w.bytes[n+4] = byte(bits >> 32) - w.bytes[n+5] = byte(bits >> 40) - n += 6 - if n >= bufferFlushSize { - if w.err != nil { - n = 0 - return - } - w.write(w.bytes[:n]) + w.writeOutBits() + } +} + +// writeOutBits will write bits to the buffer. +func (w *huffmanBitWriter) writeOutBits() { + bits := w.bits + w.bits >>= 48 + w.nbits -= 48 + n := w.nbytes + w.bytes[n] = byte(bits) + w.bytes[n+1] = byte(bits >> 8) + w.bytes[n+2] = byte(bits >> 16) + w.bytes[n+3] = byte(bits >> 24) + w.bytes[n+4] = byte(bits >> 32) + w.bytes[n+5] = byte(bits >> 40) + n += 6 + if n >= bufferFlushSize { + if w.err != nil { n = 0 + return } - w.nbytes = n + w.write(w.bytes[:n]) + n = 0 } + w.nbytes = n } // Write the header of a dynamic Huffman block to the output stream. @@ -721,35 +707,14 @@ func (w *huffmanBitWriter) writeBlockHuff(eof bool, input []byte) { // Huffman. w.writeDynamicHeader(numLiterals, numOffsets, numCodegens, eof) encoding := w.literalEncoding.codes[:257] - n := w.nbytes for _, t := range input { // Bitwriting inlined, ~30% speedup c := encoding[t] w.bits |= uint64(c.code) << w.nbits w.nbits += uint(c.len) - if w.nbits < 48 { - continue - } - // Store 6 bytes - bits := w.bits - w.bits >>= 48 - w.nbits -= 48 - w.bytes[n] = byte(bits) - w.bytes[n+1] = byte(bits >> 8) - w.bytes[n+2] = byte(bits >> 16) - w.bytes[n+3] = byte(bits >> 24) - w.bytes[n+4] = byte(bits >> 32) - w.bytes[n+5] = byte(bits >> 40) - n += 6 - if n < bufferFlushSize { - continue + if w.nbits >= 48 { + w.writeOutBits() } - w.write(w.bytes[:n]) - if w.err != nil { - return // Return early in the event of write failures - } - n = 0 } - w.nbytes = n w.writeCode(encoding[endBlockMarker]) } From d7af83e35345bc37b7413ca023ca53a917cdff48 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Fri, 14 Jun 2019 22:29:33 +0200 Subject: [PATCH 27/54] Tweak AddMatch for inlining. --- flate/token.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/flate/token.go b/flate/token.go index 5746502ec2..29a891d03a 100644 --- a/flate/token.go +++ b/flate/token.go @@ -190,10 +190,14 @@ func (t *tokens) EstimatedBits() int { return int(shannon) + bits } +// AddMatch adds a match to the tokens. +// This function is very sensitive to inlining and right on the border. func (t *tokens) AddMatch(xlength uint32, xoffset uint32) { - t.tokens[t.n] = token(matchType + uint32(xlength)< Date: Fri, 14 Jun 2019 22:56:47 +0200 Subject: [PATCH 28/54] Re-add bounds check avoidance. Credit to @egonelbre :) --- flate/token.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/flate/token.go b/flate/token.go index 29a891d03a..330759d645 100644 --- a/flate/token.go +++ b/flate/token.go @@ -195,9 +195,7 @@ func (t *tokens) EstimatedBits() int { func (t *tokens) AddMatch(xlength uint32, xoffset uint32) { t.tokens[t.n] = token(matchType | xlength<>7 < uint32(len(offsetCodes)) { - return offsetCodes[(off>>7)&255] + 14 + return offsetCodes[uint8(off>>7)] + 14 } else { - return offsetCodes[(off>>14)&255] + 28 + return offsetCodes[uint8(off>>14)] + 28 } } From 9f407f50b1b6446f08be434b5e3c2ce93856e050 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Fri, 14 Jun 2019 23:04:57 +0200 Subject: [PATCH 29/54] Use check without shift for offsetCode - @egonelbre strikes again! --- flate/token.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flate/token.go b/flate/token.go index 330759d645..6c98250cf5 100644 --- a/flate/token.go +++ b/flate/token.go @@ -231,7 +231,7 @@ func lengthCode(len uint8) uint32 { return uint32(lengthCodes[len]) } func offsetCode(off uint32) uint32 { if off < uint32(len(offsetCodes)) { return offsetCodes[uint8(off)] - } else if off>>7 < uint32(len(offsetCodes)) { + } else if off < uint32(len(offsetCodes))<<8-1 { return offsetCodes[uint8(off>>7)] + 14 } else { return offsetCodes[uint8(off>>14)] + 28 From 3cc1639e542a3d97ee14af5700e51070e07b059c Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Fri, 14 Jun 2019 23:22:15 +0200 Subject: [PATCH 30/54] Completely remove unneeded check. offset length can only be up to 15 bits. --- flate/token.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/flate/token.go b/flate/token.go index 6c98250cf5..224bea5c7c 100644 --- a/flate/token.go +++ b/flate/token.go @@ -231,9 +231,6 @@ func lengthCode(len uint8) uint32 { return uint32(lengthCodes[len]) } func offsetCode(off uint32) uint32 { if off < uint32(len(offsetCodes)) { return offsetCodes[uint8(off)] - } else if off < uint32(len(offsetCodes))<<8-1 { - return offsetCodes[uint8(off>>7)] + 14 - } else { - return offsetCodes[uint8(off>>14)] + 28 } + return offsetCodes[uint8(off>>7)] + 14 } From 1786eb395a9f2b408c89c80e8e8489157823f062 Mon Sep 17 00:00:00 2001 From: Egon Elbre Date: Sat, 15 Jun 2019 11:20:34 +0300 Subject: [PATCH 31/54] reorder fields and use extra tables (#125) --- flate/token.go | 61 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/flate/token.go b/flate/token.go index 224bea5c7c..3a05a08ffb 100644 --- a/flate/token.go +++ b/flate/token.go @@ -50,6 +50,35 @@ var lengthCodes = [256]uint8{ 27, 27, 27, 27, 27, 28, } +var lengthCodes1 = [256]uint8{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, + 10, 10, 11, 11, 12, 12, 13, 13, 13, 13, + 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, + 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, + 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, + 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, + 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 29, +} + var offsetCodes = [256]uint32{ 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, @@ -69,15 +98,34 @@ var offsetCodes = [256]uint32{ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, } +var offsetCodes14 = [256]uint32{ + 14, 15, 16, 17, 18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +} + type token uint32 type tokens struct { nLits int - n uint16 // Must be able to contain maxStoreBlockSize - tokens [maxStoreBlockSize + 1]token - litHist [256]uint16 // codes 0->255 extraHist [32]uint16 // codes 256->maxnumlit offHist [32]uint16 // offset codes + litHist [256]uint16 // codes 0->255 + n uint16 // Must be able to contain maxStoreBlockSize + tokens [maxStoreBlockSize + 1]token } func (t *tokens) Reset() { @@ -193,10 +241,11 @@ func (t *tokens) EstimatedBits() int { // AddMatch adds a match to the tokens. // This function is very sensitive to inlining and right on the border. func (t *tokens) AddMatch(xlength uint32, xoffset uint32) { + t.nLits++ + lengthCode := lengthCodes1[uint8(xlength)] & 31 t.tokens[t.n] = token(matchType | xlength<>7)] + 14 + return offsetCodes14[uint8(off>>7)] } From 74552842417f10dd6d3e7258e74d92a8fe9b0703 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Sat, 15 Jun 2019 11:47:15 +0200 Subject: [PATCH 32/54] Add test/benchmark for EstimatedBits --- flate/testdata/tokens.bin | 63 +++++++++++++++++++++++++++++++++++++++ flate/token.go | 42 ++++++++++++++++++++++++-- flate/token_test.go | 44 +++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 flate/testdata/tokens.bin create mode 100644 flate/token_test.go diff --git a/flate/testdata/tokens.bin b/flate/testdata/tokens.bin new file mode 100644 index 0000000000..b93c6968ab --- /dev/null +++ b/flate/testdata/tokens.bin @@ -0,0 +1,63 @@ + + + name>Wikip끀en./Main_PageMediaWiki 1.6alphaSpecial0" /ɀ1">Talkŀ2">User3 t؀4">܂݀5 6">Image7ڀ89 10">Template 1Ӄ 2">Helpހ3ڀ4">Category5 00">Port101Às҈AaA12005-12-27T18:46:47Z ؀ 614213쁀Ӏ#REDIRECT [[AAA]]adding cur_id=5: {{R from CamelCase}}ҀԂa]]ԀmericanSamoaɂ69ԃˁ4:1to6 ۂ݂ ނppliedEthics858989432-02-25T15:43:11ip>Con script + <Afghaany132002-08-27T03:07:44ZMagnusskewhoops׀<Þ xml:space="rve">#REDIRECT [[҂GeoȀ쁀92-25T15:43:11ip>Con꽁cript Date: Thu, 20 Jun 2019 15:13:53 +0200 Subject: [PATCH 33/54] Add huffman table reuse. --- flate/asm_test.go | 49 --------- flate/deflate.go | 5 +- flate/huffman_bit_writer.go | 201 ++++++++++++++++++++++++++---------- flate/huffman_code.go | 28 +++++ flate/reverse_bits.go | 41 +------- flate/token.go | 22 +++- 6 files changed, 196 insertions(+), 150 deletions(-) diff --git a/flate/asm_test.go b/flate/asm_test.go index 0a04f2376d..d0f03b66ed 100644 --- a/flate/asm_test.go +++ b/flate/asm_test.go @@ -142,52 +142,3 @@ func matchLenReference(a, b []byte, max int) int { } return max } - -func TestHistogram(t *testing.T) { - if !useSSE42 { - t.Skip("Skipping Matchlen test, no SSE 4.2 available") - } - // Maximum length tested - const maxLen = 65536 - var maxOff = 8 - - // Skips per iteration - is, js := 5, 3 - if testing.Short() { - is, js = 9, 1 - maxOff = 1 - } - - a := make([]byte, maxLen+maxOff) - rand.Seed(1) - for i := range a { - a[i] = byte(rand.Int63()) - } - - // Test different lengths - for i := 0; i <= maxLen; i += is { - // Test different offsets - for j := 0; j < maxOff; j += js { - var got [256]uint16 - var reference [256]uint16 - - histogram(a[j:i+j], got[:]) - histogramReference(a[j:i+j], reference[:]) - for k := range got { - if got[k] != reference[k] { - t.Fatalf("mismatch at len:%d, offset:%d, value %d: (got) %d != %d (expected)", i, j, k, got[k], reference[k]) - } - } - } - } -} - -// histogramReference is a reference -func histogramReference(b []byte, h []uint16) { - if len(h) < 256 { - panic("Histogram too small") - } - for _, t := range b { - h[t]++ - } -} diff --git a/flate/deflate.go b/flate/deflate.go index 58d84b3975..59dc995a16 100644 --- a/flate/deflate.go +++ b/flate/deflate.go @@ -1075,7 +1075,7 @@ func (d *compressor) storeHuff() { // Any error that occurred will be in d.err func (d *compressor) storeFast() { // We only compress if we have maxStoreBlockSize. - if d.windowEnd < maxStoreBlockSize { + if d.windowEnd < len(d.window) { if !d.sync { return } @@ -1156,6 +1156,7 @@ func (d *compressor) init(w io.Writer, level int) (err error) { d.fill = (*compressor).fillBlock d.step = (*compressor).store case level == ConstantCompression: + d.w.logReusePenalty = uint(4) d.window = make([]byte, maxStoreBlockSize) d.fill = (*compressor).fillBlock d.step = (*compressor).storeHuff @@ -1163,11 +1164,13 @@ func (d *compressor) init(w io.Writer, level int) (err error) { level = 5 fallthrough case level >= 1 && level <= 6: + d.w.logReusePenalty = uint(level + 1) d.snap = newFastEnc(level) d.window = make([]byte, maxStoreBlockSize) d.fill = (*compressor).fillBlock d.step = (*compressor).storeFast case 7 <= level && level <= 9: + d.w.logReusePenalty = uint(level) d.state = &advancedState{} d.compressionLevel = levels[level] d.initDeflate() diff --git a/flate/huffman_bit_writer.go b/flate/huffman_bit_writer.go index ce4c31d675..51a65eb8f9 100644 --- a/flate/huffman_bit_writer.go +++ b/flate/huffman_bit_writer.go @@ -86,8 +86,6 @@ type huffmanBitWriter struct { // and then the low nbits of bits. bits uint64 nbits uint - bytes [256]byte - codegenFreq [codegenCodeCount]uint16 nbytes uint8 literalFreq []uint16 offsetFreq []uint16 @@ -96,6 +94,12 @@ type huffmanBitWriter struct { offsetEncoding *huffmanEncoder codegenEncoding *huffmanEncoder err error + lastHeader int + // Set between 0 (reused block can be up to 2x the size) + logReusePenalty uint + lastHuffMan bool + bytes [256]byte + codegenFreq [codegenCodeCount]uint16 } func newHuffmanBitWriter(w io.Writer) *huffmanBitWriter { @@ -114,6 +118,7 @@ func (w *huffmanBitWriter) reset(writer io.Writer) { w.writer = writer w.bits, w.nbits, w.nbytes, w.err = 0, 0, 0, nil w.bytes = [256]byte{} + w.lastHeader = 0 } func (w *huffmanBitWriter) canReuse(t *tokens) (offsets, lits bool) { @@ -306,17 +311,21 @@ func (w *huffmanBitWriter) generateCodegen(numLiterals int, numOffsets int, litE codegen[outIndex] = badCode } -// dynamicSize returns the size of dynamically encoded data in bits. -func (w *huffmanBitWriter) dynamicSize(litEnc, offEnc *huffmanEncoder, extraBits int) (size, numCodegens int) { +func (w *huffmanBitWriter) headerSize() (size, numCodegens int) { numCodegens = len(w.codegenFreq) for numCodegens > 4 && w.codegenFreq[codegenOrder[numCodegens-1]] == 0 { numCodegens-- } - header := 3 + 5 + 5 + 4 + (3 * numCodegens) + + return 3 + 5 + 5 + 4 + (3 * numCodegens) + w.codegenEncoding.bitLength(w.codegenFreq[:]) + int(w.codegenFreq[16])*2 + int(w.codegenFreq[17])*3 + - int(w.codegenFreq[18])*7 + int(w.codegenFreq[18])*7, numCodegens +} + +// dynamicSize returns the size of dynamically encoded data in bits. +func (w *huffmanBitWriter) dynamicSize(litEnc, offEnc *huffmanEncoder, extraBits int) (size, numCodegens int) { + header, numCodegens := w.headerSize() size = header + litEnc.bitLength(w.literalFreq) + offEnc.bitLength(w.offsetFreq) + @@ -443,6 +452,11 @@ func (w *huffmanBitWriter) writeStoredHeader(length int, isEof bool) { if w.err != nil { return } + if w.lastHeader > 0 { + // We owe an EOB + w.writeCode(w.literalEncoding.codes[endBlockMarker]) + w.lastHeader = 0 + } var flag int32 if isEof { flag = 1 @@ -457,6 +471,12 @@ func (w *huffmanBitWriter) writeFixedHeader(isEof bool) { if w.err != nil { return } + if w.lastHeader > 0 { + // We owe an EOB + w.writeCode(w.literalEncoding.codes[endBlockMarker]) + w.lastHeader = 0 + } + // Indicate that we are a fixed Huffman block var value int32 = 2 if isEof { @@ -476,23 +496,18 @@ func (w *huffmanBitWriter) writeBlock(tokens *tokens, eof bool, input []byte) { } tokens.AddEOB() + if w.lastHeader > 0 { + // We owe an EOB + w.writeCode(w.literalEncoding.codes[endBlockMarker]) + w.lastHeader = 0 + } + w.lastHeader = 0 numLiterals, numOffsets := w.indexTokens(tokens) - + w.generate(tokens) var extraBits int storedSize, storable := w.storedSize(input) if storable { - // We only bother calculating the costs of the extra bits required by - // the length of offset fields (which will be the same for both fixed - // and dynamic encoding), if we need to compare those two encodings - // against stored encoding. - for lengthCode := lengthCodesStart + 8; lengthCode < numLiterals; lengthCode++ { - // First eight length codes have extra size = 0. - extraBits += int(w.literalFreq[lengthCode]) * int(lengthExtraBits[lengthCode-lengthCodesStart]) - } - for offsetCode := 4; offsetCode < numOffsets; offsetCode++ { - // First four offset codes have extra size = 0. - extraBits += int(w.offsetFreq[offsetCode]) * int(offsetExtraBits[offsetCode&63]) - } + extraBits = w.extraBitSize() } // Figure out smallest code. @@ -544,27 +559,69 @@ func (w *huffmanBitWriter) writeBlockDynamic(tokens *tokens, eof bool, input []b return } - tokens.AddEOB() + if eof { + tokens.AddEOB() + } + + tokens.Fill() numLiterals, numOffsets := w.indexTokens(tokens) - // Generate codegen and codegenFrequencies, which indicates how to encode - // the literalEncoding and the offsetEncoding. - w.generateCodegen(numLiterals, numOffsets, w.literalEncoding, w.offsetEncoding) - w.codegenEncoding.generate(w.codegenFreq[:], 7) - size, numCodegens := w.dynamicSize(w.literalEncoding, w.offsetEncoding, w.extraBitSize()) - //fmt.Println("dynamicSize:", size>>3, "calc:", tokens.EstimatedBits()>>3) - // Store bytes, if we don't get a reasonable improvement. - if ssize, storable := w.storedSize(input); storable && ssize < (size+size>>4) { - w.writeStoredHeader(len(input), eof) - w.writeBytes(input) - return + var size int + // Check if we should reuse. + if w.lastHeader > 0 && !w.lastHuffMan { + // Estimate size for using a new table + newSize := w.lastHeader + tokens.EstimatedBits() + // The estimated size is calculated as an optimal table. + // We add a penalty to make it more realistic and re-use a bit more. + newSize += newSize >> (w.logReusePenalty & 31) + extra := w.extraBitSize() + reuseSize, _ := w.dynamicSize(w.literalEncoding, w.offsetEncoding, extra) + //fmt.Println("Reuse Size:", reuseSize, "New Size:", newSize, "log", w.logReusePenalty, "header", w.lastHeader, "re extra:", extra, "re-main", reuseSize-extra) + if newSize < reuseSize { + // Write the EOB we owe. + size = newSize + w.lastHeader = 0 + w.writeCode(w.literalEncoding.codes[endBlockMarker]) + } else { + size = reuseSize + } + // Check if we get a reasonable size decrease. + if ssize, storable := w.storedSize(input); storable && ssize < (size+size>>4) { + w.writeStoredHeader(len(input), eof) + w.writeBytes(input) + w.lastHeader = 0 + return + } } - // Write Huffman table. - w.writeDynamicHeader(numLiterals, numOffsets, numCodegens, eof) + // We want a new block + if w.lastHeader == 0 { + w.generate(tokens) + // Generate codegen and codegenFrequencies, which indicates how to encode + // the literalEncoding and the offsetEncoding. + w.generateCodegen(numLiterals, numOffsets, w.literalEncoding, w.offsetEncoding) + w.codegenEncoding.generate(w.codegenFreq[:], 7) + var numCodegens int + size, numCodegens = w.dynamicSize(w.literalEncoding, w.offsetEncoding, w.extraBitSize()) + // Store bytes, if we don't get a reasonable improvement. + if ssize, storable := w.storedSize(input); storable && ssize < (size+size>>4) { + w.writeStoredHeader(len(input), eof) + w.writeBytes(input) + w.lastHeader = 0 + return + } + + // Write Huffman table. + w.writeDynamicHeader(numLiterals, numOffsets, numCodegens, eof) + if !eof { + w.lastHeader, _ = w.headerSize() + } + w.lastHuffMan = false + } // Write the tokens. w.writeTokens(tokens.Slice(), w.literalEncoding.codes, w.offsetEncoding.codes) + } // indexTokens indexes a slice of tokens, and updates @@ -596,9 +653,12 @@ func (w *huffmanBitWriter) indexTokens(t *tokens) (numLiterals, numOffsets int) w.offsetFreq[0] = 1 numOffsets = 1 } + return +} + +func (w *huffmanBitWriter) generate(t *tokens) { w.literalEncoding.generate(w.literalFreq[:maxNumLit], 15) w.offsetEncoding.generate(w.offsetFreq[:offsetCodeCount], 15) - return } // writeTokens writes a slice of tokens to the output. @@ -659,7 +719,6 @@ var huffOffset *huffmanEncoder func init() { w := newHuffmanBitWriter(nil) - w.offsetFreq[0] = 1 huffOffset = newHuffmanEncoder(offsetCodeCount) huffOffset.generate(w.offsetFreq[:offsetCodeCount], 15) } @@ -676,36 +735,61 @@ func (w *huffmanBitWriter) writeBlockHuff(eof bool, input []byte) { for i := range w.literalFreq { w.literalFreq[i] = 0 } + if !w.lastHuffMan { + for i := range w.offsetFreq { + w.offsetFreq[i] = 0 + } + } // Add everything as literals - histogram(input, w.literalFreq) - - w.literalFreq[endBlockMarker] = 1 - - const numLiterals = endBlockMarker + 1 - const numOffsets = 1 - - w.literalEncoding.generate(w.literalFreq[:maxNumLit], 15) - - // Figure out smallest code. - // Always use dynamic Huffman or Store - var numCodegens int - - // Generate codegen and codegenFrequencies, which indicates how to encode - // the literalEncoding and the offsetEncoding. - w.generateCodegen(numLiterals, numOffsets, w.literalEncoding, huffOffset) - w.codegenEncoding.generate(w.codegenFreq[:], 7) - size, numCodegens := w.dynamicSize(w.literalEncoding, huffOffset, 0) + estBits := histogramSize(input, w.literalFreq, !eof, 15) + 15 // Store bytes, if we don't get a reasonable improvement. - if ssize, storable := w.storedSize(input); storable && ssize < (size+size>>4) { + ssize, storable := w.storedSize(input) + if storable && ssize < (estBits+estBits>>4) { w.writeStoredHeader(len(input), eof) w.writeBytes(input) return } - // Huffman. - w.writeDynamicHeader(numLiterals, numOffsets, numCodegens, eof) + if w.lastHeader > 0 { + size, _ := w.dynamicSize(w.literalEncoding, huffOffset, w.lastHeader) + estBits += estBits >> (w.logReusePenalty) + + if estBits < size { + // We owe an EOB + w.writeCode(w.literalEncoding.codes[endBlockMarker]) + w.lastHeader = 0 + } + } + + const numLiterals = endBlockMarker + 1 + const numOffsets = 0 + if w.lastHeader == 0 { + w.literalFreq[endBlockMarker] = 1 + w.literalEncoding.generate(w.literalFreq[:numLiterals], 15) + // Figure out smallest code. + // Always use dynamic Huffman or Store + var numCodegens int + + // Generate codegen and codegenFrequencies, which indicates how to encode + // the literalEncoding and the offsetEncoding. + w.generateCodegen(numLiterals, numOffsets, w.literalEncoding, huffOffset) + w.codegenEncoding.generate(w.codegenFreq[:], 7) + size, numCodegens := w.dynamicSize(w.literalEncoding, huffOffset, 0) + + // Store bytes, if we don't get a reasonable improvement. + if ssize, storable := w.storedSize(input); storable && ssize < (size+size>>4) { + w.writeStoredHeader(len(input), eof) + w.writeBytes(input) + return + } + // Huffman. + w.writeDynamicHeader(numLiterals, numOffsets, numCodegens, eof) + w.lastHuffMan = true + w.lastHeader, _ = w.headerSize() + } + encoding := w.literalEncoding.codes[:257] for _, t := range input { // Bitwriting inlined, ~30% speedup @@ -716,5 +800,8 @@ func (w *huffmanBitWriter) writeBlockHuff(eof bool, input []byte) { w.writeOutBits() } } - w.writeCode(encoding[endBlockMarker]) + if eof { + w.writeCode(encoding[endBlockMarker]) + w.lastHeader = 0 + } } diff --git a/flate/huffman_code.go b/flate/huffman_code.go index d0b9db7a83..b50e26151d 100644 --- a/flate/huffman_code.go +++ b/flate/huffman_code.go @@ -355,3 +355,31 @@ func histogram(b []byte, h []uint16) { h[t]++ } } + +// histogram accumulates a histogram of b in h. +// +// len(h) must be >= 256, and h's elements must be all zeroes. +func histogramSize(b []byte, h []uint16, fill bool, maxBits int) int { + h = h[:256] + for _, t := range b { + h[t]++ + } + invTotal := 1.0 / float64(len(b)) + fMaxBits := float64(maxBits) + shannon := 0.0 + for i, v := range h[:] { + if v > 0 { + n := float64(v) + shannon += math.Ceil(-math.Log2(n*invTotal) * n) + if v == math.MaxUint16 { + // math.MaxUint16 is used as a magic number in the counting, + // so it cannot be in the histograms. + h[i] = math.MaxUint16 - 1 + } + } else if fill { + h[i] = 1 + shannon += fMaxBits + } + } + return int(shannon + 0.99) +} diff --git a/flate/reverse_bits.go b/flate/reverse_bits.go index c1a02720d1..09b1ade234 100644 --- a/flate/reverse_bits.go +++ b/flate/reverse_bits.go @@ -4,45 +4,8 @@ package flate -var reverseByte = [256]byte{ - 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, - 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, - 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, - 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, - 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, - 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, - 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, - 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, - 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, - 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, - 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, - 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, - 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, - 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, - 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, - 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, - 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, - 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, - 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, - 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, - 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, - 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, - 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, - 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, - 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, - 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, - 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, - 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, - 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, - 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, - 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, - 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff, -} - -func reverseUint16(v uint16) uint16 { - return uint16(reverseByte[v>>8]) | uint16(reverseByte[v&0xFF])<<8 -} +import "math/bits" func reverseBits(number uint16, bitLength byte) uint16 { - return reverseUint16(number << uint8(16-bitLength)) + return bits.Reverse16(number << uint8(16-bitLength)) } diff --git a/flate/token.go b/flate/token.go index 36f838116b..586f17fb29 100644 --- a/flate/token.go +++ b/flate/token.go @@ -154,19 +154,33 @@ func (t *tokens) Fill() { if t.n == 0 { return } - for i := range t.litHist[:] { - if t.litHist[i] == 0 { + for i, v := range t.litHist[:] { + if v == 0 { t.litHist[i] = 1 + t.nLits++ + } else if v == math.MaxUint16 { + // math.MaxUint16 is used as a magic number in the counting, + // so it cannot be in the histograms. + t.litHist[i] = math.MaxUint16 - 1 } } - for i := range t.extraHist[:maxNumLit-256] { + for i, v := range t.extraHist[:maxNumLit-256] { if t.extraHist[i] == 0 { + t.nLits++ t.extraHist[i] = 1 + } else if v == math.MaxUint16 { + // math.MaxUint16 is used as a magic number in the counting, + // so it cannot be in the histograms. + t.litHist[i] = math.MaxUint16 - 1 } } - for i := range t.offHist[:offsetCodeCount] { + for i, v := range t.offHist[:offsetCodeCount] { if t.offHist[i] == 0 { t.offHist[i] = 1 + } else if v == math.MaxUint16 { + // math.MaxUint16 is used as a magic number in the counting, + // so it cannot be in the histograms. + t.litHist[i] = math.MaxUint16 - 1 } } } From 1259b8e5414ba90163bb37d3188e44ea848f463e Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Thu, 20 Jun 2019 15:51:01 +0200 Subject: [PATCH 34/54] Manually inline code writing. --- flate/huffman_bit_writer.go | 55 +++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/flate/huffman_bit_writer.go b/flate/huffman_bit_writer.go index 51a65eb8f9..dd2b778745 100644 --- a/flate/huffman_bit_writer.go +++ b/flate/huffman_bit_writer.go @@ -85,7 +85,7 @@ type huffmanBitWriter struct { // Data waiting to be written is bytes[0:nbytes] // and then the low nbits of bits. bits uint64 - nbits uint + nbits uint16 nbytes uint8 literalFreq []uint16 offsetFreq []uint16 @@ -182,8 +182,8 @@ func (w *huffmanBitWriter) write(b []byte) { _, w.err = w.writer.Write(b) } -func (w *huffmanBitWriter) writeBits(b int32, nb uint) { - w.bits |= uint64(b) << w.nbits +func (w *huffmanBitWriter) writeBits(b int32, nb uint16) { + w.bits |= uint64(b) << (w.nbits & 63) w.nbits += nb if w.nbits >= 48 { w.writeOutBits() @@ -368,8 +368,9 @@ func (w *huffmanBitWriter) storedSize(in []byte) (int, bool) { } func (w *huffmanBitWriter) writeCode(c hcode) { + // The function does not get inlined if we "& 63" the shift. w.bits |= uint64(c.code) << w.nbits - w.nbits += uint(c.len) + w.nbits += c.len if w.nbits >= 48 { w.writeOutBits() } @@ -692,8 +693,15 @@ func (w *huffmanBitWriter) writeTokens(tokens []token, leCodes, oeCodes []hcode) // Write the length length := t.length() lengthCode := lengthCode(length) - w.writeCode(lengths[lengthCode&31]) - extraLengthBits := uint(lengthExtraBits[lengthCode&31]) + //w.writeCode(lengths[lengthCode&31]) + c := lengths[lengthCode&31] + w.bits |= uint64(c.code) << (w.nbits & 63) + w.nbits += c.len + if w.nbits >= 48 { + w.writeOutBits() + } + + extraLengthBits := uint16(lengthExtraBits[lengthCode&31]) if extraLengthBits > 0 { extraLength := int32(length - lengthBase[lengthCode&31]) w.writeBits(extraLength, extraLengthBits) @@ -701,8 +709,14 @@ func (w *huffmanBitWriter) writeTokens(tokens []token, leCodes, oeCodes []hcode) // Write the offset offset := t.offset() offsetCode := offsetCode(offset) - w.writeCode(offs[offsetCode&31]) - extraOffsetBits := uint(offsetExtraBits[offsetCode&63]) + // w.writeCode(offs[offsetCode&31]) + c = offs[offsetCode&31] + w.bits |= uint64(c.code) << (w.nbits & 63) + w.nbits += c.len + if w.nbits >= 48 { + w.writeOutBits() + } + extraOffsetBits := uint16(offsetExtraBits[offsetCode&63]) if extraOffsetBits > 0 { extraOffset := int32(offset - offsetBase[offsetCode&63]) w.writeBits(extraOffset, extraOffsetBits) @@ -794,10 +808,29 @@ func (w *huffmanBitWriter) writeBlockHuff(eof bool, input []byte) { for _, t := range input { // Bitwriting inlined, ~30% speedup c := encoding[t] - w.bits |= uint64(c.code) << w.nbits - w.nbits += uint(c.len) + w.bits |= uint64(c.code) << ((w.nbits) & 63) + w.nbits += c.len if w.nbits >= 48 { - w.writeOutBits() + bits := w.bits + w.bits >>= 48 + w.nbits -= 48 + n := w.nbytes + w.bytes[n] = byte(bits) + w.bytes[n+1] = byte(bits >> 8) + w.bytes[n+2] = byte(bits >> 16) + w.bytes[n+3] = byte(bits >> 24) + w.bytes[n+4] = byte(bits >> 32) + w.bytes[n+5] = byte(bits >> 40) + n += 6 + if n >= bufferFlushSize { + if w.err != nil { + n = 0 + return + } + w.write(w.bytes[:n]) + n = 0 + } + w.nbytes = n } } if eof { From ae21d9c5c4e5b4b7e84b19ff90dd773d59f584f5 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Thu, 20 Jun 2019 21:35:44 +0200 Subject: [PATCH 35/54] Fix huffman code generation. --- flate/huffman_bit_writer.go | 22 ++++++++-------- flate/huffman_code.go | 51 +++++++++++++++++-------------------- flate/level1.go | 21 ++------------- flate/level2.go | 28 ++++++++++---------- flate/token.go | 20 +++------------ 5 files changed, 54 insertions(+), 88 deletions(-) diff --git a/flate/huffman_bit_writer.go b/flate/huffman_bit_writer.go index dd2b778745..46d8db0158 100644 --- a/flate/huffman_bit_writer.go +++ b/flate/huffman_bit_writer.go @@ -104,11 +104,12 @@ type huffmanBitWriter struct { func newHuffmanBitWriter(w io.Writer) *huffmanBitWriter { return &huffmanBitWriter{ - writer: w, - literalFreq: make([]uint16, lengthCodesStart+32), - offsetFreq: make([]uint16, 32), - codegen: make([]uint8, maxNumLit+offsetCodeCount+1), - literalEncoding: newHuffmanEncoder(maxNumLit), + writer: w, + literalFreq: make([]uint16, lengthCodesStart+32), + offsetFreq: make([]uint16, 32), + // codegen must have an extra space for the final symbol. + codegen: make([]uint8, literalCount+offsetCodeCount+1), + literalEncoding: newHuffmanEncoder(literalCount), codegenEncoding: newHuffmanEncoder(codegenCodeCount), offsetEncoding: newHuffmanEncoder(offsetCodeCount), } @@ -132,8 +133,8 @@ func (w *huffmanBitWriter) canReuse(t *tokens) (offsets, lits bool) { } } - a = t.extraHist[:maxNumLit-256] - b = w.literalFreq[256:maxNumLit] + a = t.extraHist[:literalCount-256] + b = w.literalFreq[256:literalCount] b = b[:len(a)] for i := range a { if b[i] == 0 && a[i] != 0 { @@ -337,7 +338,7 @@ func (w *huffmanBitWriter) dynamicSize(litEnc, offEnc *huffmanEncoder, extraBits // as "extra" bits on matches. func (w *huffmanBitWriter) extraBitSize() int { total := 0 - for i, n := range w.literalFreq[257:maxNumLit] { + for i, n := range w.literalFreq[257:literalCount] { total += int(n) * int(lengthExtraBits[i&31]) } for i, n := range w.offsetFreq[:offsetCodeCount] { @@ -658,7 +659,7 @@ func (w *huffmanBitWriter) indexTokens(t *tokens) (numLiterals, numOffsets int) } func (w *huffmanBitWriter) generate(t *tokens) { - w.literalEncoding.generate(w.literalFreq[:maxNumLit], 15) + w.literalEncoding.generate(w.literalFreq[:literalCount], 15) w.offsetEncoding.generate(w.offsetFreq[:offsetCodeCount], 15) } @@ -733,6 +734,7 @@ var huffOffset *huffmanEncoder func init() { w := newHuffmanBitWriter(nil) + w.offsetFreq[0] = 1 huffOffset = newHuffmanEncoder(offsetCodeCount) huffOffset.generate(w.offsetFreq[:offsetCodeCount], 15) } @@ -778,7 +780,7 @@ func (w *huffmanBitWriter) writeBlockHuff(eof bool, input []byte) { } const numLiterals = endBlockMarker + 1 - const numOffsets = 0 + const numOffsets = 1 if w.lastHeader == 0 { w.literalFreq[endBlockMarker] = 1 w.literalEncoding.generate(w.literalFreq[:numLiterals], 15) diff --git a/flate/huffman_code.go b/flate/huffman_code.go index b50e26151d..0ee90375e9 100644 --- a/flate/huffman_code.go +++ b/flate/huffman_code.go @@ -10,6 +10,12 @@ import ( "sort" ) +const ( + maxBitsLimit = 16 + // number of valid literals + literalCount = 286 +) + // hcode is a huffman code with a bit code and bit length. type hcode struct { code, len uint16 @@ -64,10 +70,10 @@ func newHuffmanEncoder(size int) *huffmanEncoder { // Generates a HuffmanCode corresponding to the fixed literal table func generateFixedLiteralEncoding() *huffmanEncoder { - h := newHuffmanEncoder(maxNumLit) + h := newHuffmanEncoder(literalCount) codes := h.codes var ch uint16 - for ch = 0; ch < maxNumLit; ch++ { + for ch = 0; ch < literalCount; ch++ { var bits uint16 var size uint16 switch { @@ -118,8 +124,6 @@ func (h *huffmanEncoder) bitLength(freq []uint16) int { return total } -const maxBitsLimit = 16 - // Return the number of literals assigned to each bit size in the Huffman encoding // // This method is only called when list.length >= 3 @@ -169,7 +173,7 @@ func (h *huffmanEncoder) bitCounts(list []literalNode, maxBits int32) []int32 { } leafCounts[level][level] = 2 if level == 1 { - levels[level].nextPairFreq = math.MaxUint16 + levels[level].nextPairFreq = math.MaxInt32 } } @@ -179,13 +183,13 @@ func (h *huffmanEncoder) bitCounts(list []literalNode, maxBits int32) []int32 { level := maxBits for { l := &levels[level] - if l.nextPairFreq == math.MaxUint16 && l.nextCharFreq == math.MaxUint16 { + if l.nextPairFreq == math.MaxInt32 && l.nextCharFreq == math.MaxInt32 { // We've run out of both leafs and pairs. // End all calculations for this level. // To make sure we never come back to this level or any lower level, // set nextPairFreq impossibly large. l.needed = 0 - levels[level+1].nextPairFreq = math.MaxUint16 + levels[level+1].nextPairFreq = math.MaxInt32 level++ continue } @@ -197,7 +201,12 @@ func (h *huffmanEncoder) bitCounts(list []literalNode, maxBits int32) []int32 { l.lastFreq = l.nextCharFreq // Lower leafCounts are the same of the previous node. leafCounts[level][level] = n - l.nextCharFreq = int32(list[n].freq) + e := list[n] + if e.literal < math.MaxUint16 { + l.nextCharFreq = int32(e.freq) + } else { + l.nextCharFreq = math.MaxInt32 + } } else { // The next item on this row is a pair from the previous row. // nextPairFreq isn't valid until we generate two @@ -276,9 +285,9 @@ func (h *huffmanEncoder) assignEncodingAndSize(bitCount []int32, list []literalN func (h *huffmanEncoder) generate(freq []uint16, maxBits int32) { if h.freqcache == nil { // Allocate a reusable buffer with the longest possible frequency table. - // Possible lengths are codegenCodeCount, offsetCodeCount and maxNumLit. - // The largest of these is maxNumLit, so we allocate for that case. - h.freqcache = make([]literalNode, maxNumLit+1) + // Possible lengths are codegenCodeCount, offsetCodeCount and literalCount. + // The largest of these is literalCount, so we allocate for that case. + h.freqcache = make([]literalNode, literalCount+1) } list := h.freqcache[:len(freq)+1] // Number of non-zero literals @@ -346,18 +355,9 @@ func (s byFreq) Less(i, j int) bool { func (s byFreq) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -// histogram accumulates a histogram of b in h. -// -// len(h) must be >= 256, and h's elements must be all zeroes. -func histogram(b []byte, h []uint16) { - h = h[:256] - for _, t := range b { - h[t]++ - } -} - -// histogram accumulates a histogram of b in h. -// +// histogramSize accumulates a histogram of b in h. +// An estimated size is returned. +// Unassigned values are assigned '1' and given maxBits bits. // len(h) must be >= 256, and h's elements must be all zeroes. func histogramSize(b []byte, h []uint16, fill bool, maxBits int) int { h = h[:256] @@ -371,11 +371,6 @@ func histogramSize(b []byte, h []uint16, fill bool, maxBits int) int { if v > 0 { n := float64(v) shannon += math.Ceil(-math.Log2(n*invTotal) * n) - if v == math.MaxUint16 { - // math.MaxUint16 is used as a magic number in the counting, - // so it cannot be in the histograms. - h[i] = math.MaxUint16 - 1 - } } else if fill { h[i] = 1 shannon += fMaxBits diff --git a/flate/level1.go b/flate/level1.go index 9329269d0c..17ec4f71d4 100644 --- a/flate/level1.go +++ b/flate/level1.go @@ -63,7 +63,7 @@ func (e *fastEncL1) Encode(dst *tokens, src []byte) { nextHash := hash(cv) for { - const skipLog = 6 + const skipLog = 5 const doEvery = 2 nextS := s @@ -143,25 +143,8 @@ func (e *fastEncL1) Encode(dst *tokens, src []byte) { goto emitRemainder } - // Store every second hash in-between, but offset by 1. - if false { - for i := s - l - 2; i < s-7; i += 7 { - x := load6432(src, int32(i)) - nextHash := hash(uint32(x)) - e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i, val: uint32(x)} - // Skip one - x >>= 16 - nextHash = hash(uint32(x)) - e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i + 2, val: uint32(x)} - // Skip one - x >>= 16 - nextHash = hash(uint32(x)) - e.table[nextHash&tableMask] = tableEntry{offset: e.cur + i + 4, val: uint32(x)} - } - } - // We could immediately start working at s now, but to improve - // compression we first update the hash table at s-1 and at s. If + // compression we first update the hash table at s-2 s-1 and at s. If // another emitCopy is not our next move, also calculate nextHash // at s+1. At least on GOARCH=amd64, these three hash calculations // are faster as one load64 call (with some shifts) instead of diff --git a/flate/level2.go b/flate/level2.go index f32c066836..3b1eb931f0 100644 --- a/flate/level2.go +++ b/flate/level2.go @@ -169,24 +169,22 @@ func (e *fastEncL2) Encode(dst *tokens, src []byte) { } // Store every second hash in-between, but offset by 1. - if true { - for i := s - l + 2; i < s-5; i += 7 { - x := load6432(src, int32(i)) - nextHash := hash4u(uint32(x), bTableBits) - e.table[nextHash&bTableMask] = tableEntry{offset: e.cur + i, val: uint32(x)} - // Skip one - x >>= 16 - nextHash = hash4u(uint32(x), bTableBits) - e.table[nextHash&bTableMask] = tableEntry{offset: e.cur + i + 2, val: uint32(x)} - // Skip one - x >>= 16 - nextHash = hash4u(uint32(x), bTableBits) - e.table[nextHash&bTableMask] = tableEntry{offset: e.cur + i + 4, val: uint32(x)} - } + for i := s - l + 2; i < s-5; i += 7 { + x := load6432(src, int32(i)) + nextHash := hash4u(uint32(x), bTableBits) + e.table[nextHash&bTableMask] = tableEntry{offset: e.cur + i, val: uint32(x)} + // Skip one + x >>= 16 + nextHash = hash4u(uint32(x), bTableBits) + e.table[nextHash&bTableMask] = tableEntry{offset: e.cur + i + 2, val: uint32(x)} + // Skip one + x >>= 16 + nextHash = hash4u(uint32(x), bTableBits) + e.table[nextHash&bTableMask] = tableEntry{offset: e.cur + i + 4, val: uint32(x)} } // We could immediately start working at s now, but to improve - // compression we first update the hash table at s-1 and at s. If + // compression we first update the hash table at s-2 to s. If // another emitCopy is not our next move, also calculate nextHash // at s+1. At least on GOARCH=amd64, these three hash calculations // are faster as one load64 call (with some shifts) instead of diff --git a/flate/token.go b/flate/token.go index 586f17fb29..1c353f4dff 100644 --- a/flate/token.go +++ b/flate/token.go @@ -158,29 +158,17 @@ func (t *tokens) Fill() { if v == 0 { t.litHist[i] = 1 t.nLits++ - } else if v == math.MaxUint16 { - // math.MaxUint16 is used as a magic number in the counting, - // so it cannot be in the histograms. - t.litHist[i] = math.MaxUint16 - 1 } } - for i, v := range t.extraHist[:maxNumLit-256] { - if t.extraHist[i] == 0 { + for i, v := range t.extraHist[:literalCount-256] { + if v == 0 { t.nLits++ t.extraHist[i] = 1 - } else if v == math.MaxUint16 { - // math.MaxUint16 is used as a magic number in the counting, - // so it cannot be in the histograms. - t.litHist[i] = math.MaxUint16 - 1 } } for i, v := range t.offHist[:offsetCodeCount] { - if t.offHist[i] == 0 { + if v == 0 { t.offHist[i] = 1 - } else if v == math.MaxUint16 { - // math.MaxUint16 is used as a magic number in the counting, - // so it cannot be in the histograms. - t.litHist[i] = math.MaxUint16 - 1 } } } @@ -239,7 +227,7 @@ func (t *tokens) EstimatedBits() int { } // Just add 15 for EOB shannon += 15 - for _, v := range t.extraHist[1 : maxNumLit-256] { + for _, v := range t.extraHist[1 : literalCount-256] { if v > 0 { n := float64(v) shannon += math.Ceil(-math.Log2(n*invTotal) * n) From 09fbddccef39838e900a9e6aac8c142428de4f5a Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Fri, 21 Jun 2019 11:51:40 +0200 Subject: [PATCH 36/54] Don't add maxbits for no-present values. --- flate/huffman_bit_writer.go | 2 +- flate/huffman_code.go | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/flate/huffman_bit_writer.go b/flate/huffman_bit_writer.go index 46d8db0158..69793c021b 100644 --- a/flate/huffman_bit_writer.go +++ b/flate/huffman_bit_writer.go @@ -758,7 +758,7 @@ func (w *huffmanBitWriter) writeBlockHuff(eof bool, input []byte) { } // Add everything as literals - estBits := histogramSize(input, w.literalFreq, !eof, 15) + 15 + estBits := histogramSize(input, w.literalFreq, !eof) + 15 // Store bytes, if we don't get a reasonable improvement. ssize, storable := w.storedSize(input) diff --git a/flate/huffman_code.go b/flate/huffman_code.go index 0ee90375e9..00b5c1a6cc 100644 --- a/flate/huffman_code.go +++ b/flate/huffman_code.go @@ -357,15 +357,14 @@ func (s byFreq) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // histogramSize accumulates a histogram of b in h. // An estimated size is returned. -// Unassigned values are assigned '1' and given maxBits bits. +// Unassigned values are assigned '1' in the histogram. // len(h) must be >= 256, and h's elements must be all zeroes. -func histogramSize(b []byte, h []uint16, fill bool, maxBits int) int { +func histogramSize(b []byte, h []uint16, fill bool) int { h = h[:256] for _, t := range b { h[t]++ } invTotal := 1.0 / float64(len(b)) - fMaxBits := float64(maxBits) shannon := 0.0 for i, v := range h[:] { if v > 0 { @@ -373,7 +372,6 @@ func histogramSize(b []byte, h []uint16, fill bool, maxBits int) int { shannon += math.Ceil(-math.Log2(n*invTotal) * n) } else if fill { h[i] = 1 - shannon += fMaxBits } } return int(shannon + 0.99) From 3a3bce515f87b71fb194ce36adfcdec740c75ae5 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Tue, 25 Jun 2019 19:30:07 +0200 Subject: [PATCH 37/54] * Add bufferReset as constant * Add inflate debug printing --- flate/fast_encoder.go | 28 ++++++++---- flate/inflate.go | 57 +++++++++++++++++++++++ flate/level1.go | 4 +- flate/level2.go | 4 +- flate/level3.go | 6 +-- flate/level4.go | 6 +-- flate/level5.go | 8 ++-- flate/level6.go | 11 +++-- flate/testdata/regression.zip | Bin 12361 -> 49754 bytes flate/writer_test.go | 84 ++++++++++++++++++++++++++++++++++ 10 files changed, 179 insertions(+), 29 deletions(-) diff --git a/flate/fast_encoder.go b/flate/fast_encoder.go index 676f5b2489..c4ef98c990 100644 --- a/flate/fast_encoder.go +++ b/flate/fast_encoder.go @@ -43,10 +43,10 @@ const ( baseMatchLength = 3 // The smallest match length per the RFC section 3.2.5 maxMatchOffset = 1 << 15 // The largest match offset - bTableBits = 18 // Bits used in the table - bTableSize = 1 << bTableBits // Size of the table - bTableMask = bTableSize - 1 // Mask for table indices. Redundant, but can eliminate bounds checks. - + bTableBits = 18 // Bits used in the table + bTableSize = 1 << bTableBits // Size of the table + bTableMask = bTableSize - 1 // Mask for table indices. Redundant, but can eliminate bounds checks. + bufferReset = (1 << 31) - (maxStoreBlockSize * 2) // Reset the buffer offset when reaching this. ) const ( @@ -182,6 +182,9 @@ func (e *fastGen) matchlen(s, t int32, src []byte) int32 { if t < 0 { panic(fmt.Sprint("t < 0 ", t)) } + if s-t > maxMatchOffset { + panic(fmt.Sprint(s, "-", t, "(", s-t, ") > maxMatchLength (", maxMatchOffset, ")")) + } } s1 := int(s) + maxMatchLength - 4 if s1 > len(src) { @@ -218,15 +221,20 @@ func matchLen(a, b []byte) int { return bits.TrailingZeros32(diff) >> 3 } // Switch to 8 byte matching. - for i := 4; i < len(a)-7; i += 8 { - if diff := load64(a, i) ^ load64(b, i); diff != 0 { - return i + (bits.TrailingZeros64(diff) >> 3) + checked = 4 + a = a[4:] + b = b[4:] + for len(a) >= 8 { + b = b[:len(a)] + if diff := load64(a, 0) ^ load64(b, 0); diff != 0 { + return checked + (bits.TrailingZeros64(diff) >> 3) } + checked += 8 + a = a[8:] + b = b[8:] } - checked = 4 + ((len(a)-4)>>3)<<3 - a = a[checked:] - b = b[checked:] } + b = b[:len(a)] for i := range a { if a[i] != b[i] { return int(i) + checked diff --git a/flate/inflate.go b/flate/inflate.go index 800d0ce9e5..4a9028d87b 100644 --- a/flate/inflate.go +++ b/flate/inflate.go @@ -9,6 +9,7 @@ package flate import ( "bufio" + "fmt" "io" "math/bits" "strconv" @@ -24,6 +25,8 @@ const ( maxNumLit = 286 maxNumDist = 30 numCodes = 19 // number of codes in Huffman meta-code + + debugDecode = false ) // Initialize the fixedHuffmanDecoder only once upon first use. @@ -169,6 +172,9 @@ func (h *huffmanDecoder) init(lengths []int) bool { // accept degenerate single-code codings. See also // TestDegenerateHuffmanCoding. if code != 1< maxNumLit { + if debugDecode { + fmt.Println("nlit > maxNumLit", nlit) + } return CorruptInputError(f.roffset) } f.b >>= 5 ndist := int(f.b&0x1F) + 1 if ndist > maxNumDist { + if debugDecode { + fmt.Println("ndist > maxNumDist", ndist) + } return CorruptInputError(f.roffset) } f.b >>= 5 @@ -453,6 +468,9 @@ func (f *decompressor) readHuffman() error { f.codebits[codeOrder[i]] = 0 } if !f.h1.init(f.codebits[0:]) { + if debugDecode { + fmt.Println("init codebits failed") + } return CorruptInputError(f.roffset) } @@ -480,6 +498,9 @@ func (f *decompressor) readHuffman() error { rep = 3 nb = 2 if i == 0 { + if debugDecode { + fmt.Println("i==0") + } return CorruptInputError(f.roffset) } b = f.bits[i-1] @@ -494,6 +515,9 @@ func (f *decompressor) readHuffman() error { } for f.nb < nb { if err := f.moreBits(); err != nil { + if debugDecode { + fmt.Println("morebits:", err) + } return err } } @@ -501,6 +525,9 @@ func (f *decompressor) readHuffman() error { f.b >>= nb f.nb -= nb if i+rep > n { + if debugDecode { + fmt.Println("i+rep > n", i, rep, n) + } return CorruptInputError(f.roffset) } for j := 0; j < rep; j++ { @@ -510,6 +537,9 @@ func (f *decompressor) readHuffman() error { } if !f.h1.init(f.bits[0:nlit]) || !f.h2.init(f.bits[nlit:nlit+ndist]) { + if debugDecode { + fmt.Println("init2 failed") + } return CorruptInputError(f.roffset) } @@ -587,12 +617,18 @@ readLiteral: length = 258 n = 0 default: + if debugDecode { + fmt.Println(v, ">= maxNumLit") + } f.err = CorruptInputError(f.roffset) return } if n > 0 { for f.nb < n { if err = f.moreBits(); err != nil { + if debugDecode { + fmt.Println("morebits n>0:", err) + } f.err = err return } @@ -606,6 +642,9 @@ readLiteral: if f.hd == nil { for f.nb < 5 { if err = f.moreBits(); err != nil { + if debugDecode { + fmt.Println("morebits f.nb<5:", err) + } f.err = err return } @@ -615,6 +654,9 @@ readLiteral: f.nb -= 5 } else { if dist, err = f.huffSym(f.hd); err != nil { + if debugDecode { + fmt.Println("huffsym:", err) + } f.err = err return } @@ -629,6 +671,9 @@ readLiteral: extra := (dist & 1) << nb for f.nb < nb { if err = f.moreBits(); err != nil { + if debugDecode { + fmt.Println("morebits f.nb f.dict.histSize() { + if debugDecode { + fmt.Println("dist > f.dict.histSize():", dist, f.dict.histSize()) + } f.err = CorruptInputError(f.roffset) return } @@ -688,6 +739,9 @@ func (f *decompressor) dataBlock() { n := int(f.buf[0]) | int(f.buf[1])<<8 nn := int(f.buf[2]) | int(f.buf[3])<<8 if uint16(nn) != uint16(^n) { + if debugDecode { + fmt.Println("uint16(nn) != uint16(^n)", nn, ^n) + } f.err = CorruptInputError(f.roffset) return } @@ -789,6 +843,9 @@ func (f *decompressor) huffSym(h *huffmanDecoder) (int, error) { if n == 0 { f.b = b f.nb = nb + if debugDecode { + fmt.Println("huffsym: n==0") + } f.err = CorruptInputError(f.roffset) return 0, f.err } diff --git a/flate/level1.go b/flate/level1.go index 17ec4f71d4..fc780a41c2 100644 --- a/flate/level1.go +++ b/flate/level1.go @@ -16,7 +16,7 @@ func (e *fastEncL1) Encode(dst *tokens, src []byte) { ) // Protect against e.cur wraparound. - for e.cur >= (1<<31)-(maxStoreBlockSize*2) { + for e.cur >= bufferReset { if len(e.hist) == 0 { for i := range e.table[:] { e.table[i] = tableEntry{} @@ -28,7 +28,7 @@ func (e *fastEncL1) Encode(dst *tokens, src []byte) { minOff := e.cur + int32(len(e.hist)) - maxMatchOffset for i := range e.table[:] { v := e.table[i].offset - if v < minOff { + if v <= minOff { v = 0 } else { v = v - e.cur + maxMatchOffset diff --git a/flate/level2.go b/flate/level2.go index 3b1eb931f0..aa6497d8f8 100644 --- a/flate/level2.go +++ b/flate/level2.go @@ -17,7 +17,7 @@ func (e *fastEncL2) Encode(dst *tokens, src []byte) { ) // Protect against e.cur wraparound. - for e.cur >= (1<<31)-(maxStoreBlockSize*2) { + for e.cur >= bufferReset { if len(e.hist) == 0 { for i := range e.table[:] { e.table[i] = tableEntry{} @@ -29,7 +29,7 @@ func (e *fastEncL2) Encode(dst *tokens, src []byte) { minOff := e.cur + int32(len(e.hist)) - maxMatchOffset for i := range e.table[:] { v := e.table[i].offset - if v < minOff { + if v <= minOff { v = 0 } else { v = v - e.cur + maxMatchOffset diff --git a/flate/level3.go b/flate/level3.go index c14d82a2b8..9a79e001a6 100644 --- a/flate/level3.go +++ b/flate/level3.go @@ -14,7 +14,7 @@ func (e *fastEncL3) Encode(dst *tokens, src []byte) { ) // Protect against e.cur wraparound. - for e.cur >= (1<<31)-(maxStoreBlockSize) { + for e.cur >= bufferReset { if len(e.hist) == 0 { for i := range e.table[:] { e.table[i] = tableEntryPrev{} @@ -26,12 +26,12 @@ func (e *fastEncL3) Encode(dst *tokens, src []byte) { minOff := e.cur + int32(len(e.hist)) - maxMatchOffset for i := range e.table[:] { v := e.table[i] - if v.Cur.offset < minOff { + if v.Cur.offset <= minOff { v.Cur.offset = 0 } else { v.Cur.offset = v.Cur.offset - e.cur + maxMatchOffset } - if v.Prev.offset < minOff { + if v.Prev.offset <= minOff { v.Prev.offset = 0 } else { v.Prev.offset = v.Prev.offset - e.cur + maxMatchOffset diff --git a/flate/level4.go b/flate/level4.go index d0a9d69e73..80a1d9a2c5 100644 --- a/flate/level4.go +++ b/flate/level4.go @@ -9,7 +9,7 @@ func (e *fastEncL4) Encode(dst *tokens, src []byte) { ) // Protect against e.cur wraparound. - for e.cur >= (1<<31)-(maxStoreBlockSize*2) { + for e.cur >= bufferReset { if len(e.hist) == 0 { for i := range e.table[:] { e.table[i] = tableEntry{} @@ -24,7 +24,7 @@ func (e *fastEncL4) Encode(dst *tokens, src []byte) { minOff := e.cur + int32(len(e.hist)) - maxMatchOffset for i := range e.table[:] { v := e.table[i].offset - if v < minOff { + if v <= minOff { v = 0 } else { v = v - e.cur + maxMatchOffset @@ -33,7 +33,7 @@ func (e *fastEncL4) Encode(dst *tokens, src []byte) { } for i := range e.bTable[:] { v := e.bTable[i].offset - if v < minOff { + if v <= minOff { v = 0 } else { v = v - e.cur + maxMatchOffset diff --git a/flate/level5.go b/flate/level5.go index 2f7cd35478..7b8ed91dbc 100644 --- a/flate/level5.go +++ b/flate/level5.go @@ -15,7 +15,7 @@ func (e *fastEncL5) Encode(dst *tokens, src []byte) { ) // Protect against e.cur wraparound. - for e.cur >= (1<<31)-(maxStoreBlockSize*2) { + for e.cur >= bufferReset { if len(e.hist) == 0 { for i := range e.table[:] { e.table[i] = tableEntry{} @@ -30,7 +30,7 @@ func (e *fastEncL5) Encode(dst *tokens, src []byte) { minOff := e.cur + int32(len(e.hist)) - maxMatchOffset for i := range e.table[:] { v := e.table[i].offset - if v < minOff { + if v <= minOff { v = 0 } else { v = v - e.cur + maxMatchOffset @@ -39,12 +39,12 @@ func (e *fastEncL5) Encode(dst *tokens, src []byte) { } for i := range e.bTable[:] { v := e.bTable[i] - if v.Cur.offset < minOff { + if v.Cur.offset <= minOff { v.Cur.offset = 0 v.Prev.offset = 0 } else { v.Cur.offset = v.Cur.offset - e.cur + maxMatchOffset - if v.Prev.offset < minOff { + if v.Prev.offset <= minOff { v.Prev.offset = 0 } else { v.Prev.offset = v.Prev.offset - e.cur + maxMatchOffset diff --git a/flate/level6.go b/flate/level6.go index 8f712f62fb..1105817731 100644 --- a/flate/level6.go +++ b/flate/level6.go @@ -15,7 +15,7 @@ func (e *fastEncL6) Encode(dst *tokens, src []byte) { ) // Protect against e.cur wraparound. - for e.cur >= (1<<31)-(maxStoreBlockSize*2) { + for e.cur >= bufferReset { if len(e.hist) == 0 { for i := range e.table[:] { e.table[i] = tableEntry{} @@ -30,7 +30,7 @@ func (e *fastEncL6) Encode(dst *tokens, src []byte) { minOff := e.cur + int32(len(e.hist)) - maxMatchOffset for i := range e.table[:] { v := e.table[i].offset - if v < minOff { + if v <= minOff { v = 0 } else { v = v - e.cur + maxMatchOffset @@ -39,12 +39,12 @@ func (e *fastEncL6) Encode(dst *tokens, src []byte) { } for i := range e.bTable[:] { v := e.bTable[i] - if v.Cur.offset < minOff { + if v.Cur.offset <= minOff { v.Cur.offset = 0 v.Prev.offset = 0 } else { v.Cur.offset = v.Cur.offset - e.cur + maxMatchOffset - if v.Prev.offset < minOff { + if v.Prev.offset <= minOff { v.Prev.offset = 0 } else { v.Prev.offset = v.Prev.offset - e.cur + maxMatchOffset @@ -104,6 +104,7 @@ func (e *fastEncL6) Encode(dst *tokens, src []byte) { eLong := &e.bTable[nextHashL&tableMask] eLong.Cur, eLong.Prev = entry, eLong.Cur + // Calculate hashes of 'next' nextHashS = hash4x64(next, tableBits) nextHashL = hash7(next, tableBits) @@ -249,7 +250,7 @@ func (e *fastEncL6) Encode(dst *tokens, src []byte) { goto emitRemainder } - // Store every 2nd hash in-between + // Store every hash in-between if true { for i := nextS + 1; i < s; i++ { cv := load6432(src, i) diff --git a/flate/testdata/regression.zip b/flate/testdata/regression.zip index f732814264287218d8a4444d6c3fe5870aeab7d1..4daeb5e468819035d9c86f1f1b5fc44fb911a62b 100644 GIT binary patch literal 49754 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 e#BRESHQk=&^(OUAju@d${ijd8Vf0fyzx!V Date: Mon, 5 Aug 2019 12:28:34 +0200 Subject: [PATCH 38/54] Fix token writing. --- flate/deflate.go | 18 +++++----- flate/deflate_test.go | 2 +- flate/fast_encoder.go | 2 +- flate/huffman_bit_writer.go | 67 ++++++++++++++++++++++++------------- flate/huffman_code.go | 4 ++- flate/level1.go | 28 +++++++--------- flate/reverse_bits.go | 2 +- flate/token.go | 18 ++++++++++ flate/writer_test.go | 22 ++++++------ 9 files changed, 99 insertions(+), 64 deletions(-) diff --git a/flate/deflate.go b/flate/deflate.go index 59dc995a16..27ef02570a 100644 --- a/flate/deflate.go +++ b/flate/deflate.go @@ -120,7 +120,7 @@ type compressor struct { // queued output tokens tokens tokens - snap fastEnc + fast fastEnc state *advancedState } @@ -211,12 +211,12 @@ func (d *compressor) fillWindow(b []byte) { if d.level == 0 { return } - if d.snap != nil { + if d.fast != nil { // encode the last data, but discard the result if len(b) > maxMatchOffset { b = b[len(b)-maxMatchOffset:] } - d.snap.Encode(&d.tokens, b) + d.fast.Encode(&d.tokens, b) d.tokens.Reset() return } @@ -1086,20 +1086,18 @@ func (d *compressor) storeFast() { } if d.windowEnd <= 32 { d.err = d.writeStoredBlock(d.window[:d.windowEnd]) - d.tokens.Reset() - d.windowEnd = 0 } else { d.w.writeBlockHuff(false, d.window[:d.windowEnd]) d.err = d.w.err } d.tokens.Reset() d.windowEnd = 0 - d.snap.Reset() + d.fast.Reset() return } } - d.snap.Encode(&d.tokens, d.window[:d.windowEnd]) + d.fast.Encode(&d.tokens, d.window[:d.windowEnd]) // If we made zero matches, store the block as is. if int(d.tokens.n) == d.windowEnd { d.err = d.writeStoredBlock(d.window[:d.windowEnd]) @@ -1165,7 +1163,7 @@ func (d *compressor) init(w io.Writer, level int) (err error) { fallthrough case level >= 1 && level <= 6: d.w.logReusePenalty = uint(level + 1) - d.snap = newFastEnc(level) + d.fast = newFastEnc(level) d.window = make([]byte, maxStoreBlockSize) d.fill = (*compressor).fillBlock d.step = (*compressor).storeFast @@ -1201,8 +1199,8 @@ func (d *compressor) reset(w io.Writer) { d.sync = false d.err = nil // We only need to reset a few things for Snappy. - if d.snap != nil { - d.snap.Reset() + if d.fast != nil { + d.fast.Reset() d.windowEnd = 0 d.tokens.Reset() return diff --git a/flate/deflate_test.go b/flate/deflate_test.go index c5159f26bd..15838ab14d 100644 --- a/flate/deflate_test.go +++ b/flate/deflate_test.go @@ -501,7 +501,7 @@ func TestWriterReset(t *testing.T) { w.d.fill, wref.d.fill = nil, nil w.d.step, wref.d.step = nil, nil w.d.state, wref.d.state = nil, nil - w.d.snap, wref.d.snap = nil, nil + w.d.fast, wref.d.fast = nil, nil // hashMatch is always overwritten when used. if w.d.tokens.n != 0 { diff --git a/flate/fast_encoder.go b/flate/fast_encoder.go index c4ef98c990..c62164496d 100644 --- a/flate/fast_encoder.go +++ b/flate/fast_encoder.go @@ -172,7 +172,7 @@ type fastEncL4 struct { // The maximum length returned is maxMatchLength - 4. // It is assumed that s > t, that t >=0 and s < len(src). func (e *fastGen) matchlen(s, t int32, src []byte) int32 { - if false { + if debugDecode { if t >= s { panic(fmt.Sprint("t>=s", t, s)) } diff --git a/flate/huffman_bit_writer.go b/flate/huffman_bit_writer.go index 69793c021b..522578b67e 100644 --- a/flate/huffman_bit_writer.go +++ b/flate/huffman_bit_writer.go @@ -120,6 +120,7 @@ func (w *huffmanBitWriter) reset(writer io.Writer) { w.bits, w.nbits, w.nbytes, w.err = 0, 0, 0, nil w.bytes = [256]byte{} w.lastHeader = 0 + w.lastHuffMan = false } func (w *huffmanBitWriter) canReuse(t *tokens) (offsets, lits bool) { @@ -312,6 +313,14 @@ func (w *huffmanBitWriter) generateCodegen(numLiterals int, numOffsets int, litE codegen[outIndex] = badCode } +func (w *huffmanBitWriter) codegens() int { + numCodegens := len(w.codegenFreq) + for numCodegens > 4 && w.codegenFreq[codegenOrder[numCodegens-1]] == 0 { + numCodegens-- + } + return numCodegens +} + func (w *huffmanBitWriter) headerSize() (size, numCodegens int) { numCodegens = len(w.codegenFreq) for numCodegens > 4 && w.codegenFreq[codegenOrder[numCodegens-1]] == 0 { @@ -565,14 +574,23 @@ func (w *huffmanBitWriter) writeBlockDynamic(tokens *tokens, eof bool, input []b tokens.AddEOB() } + // We cannot reuse pure huffman table. + if w.lastHuffMan && w.lastHeader > 0 { + // We will not try to reuse. + w.writeCode(w.literalEncoding.codes[endBlockMarker]) + w.lastHeader = 0 + w.lastHuffMan = false + } + tokens.Fill() numLiterals, numOffsets := w.indexTokens(tokens) var size int // Check if we should reuse. - if w.lastHeader > 0 && !w.lastHuffMan { + if w.lastHeader > 0 { // Estimate size for using a new table newSize := w.lastHeader + tokens.EstimatedBits() + // The estimated size is calculated as an optimal table. // We add a penalty to make it more realistic and re-use a bit more. newSize += newSize >> (w.logReusePenalty & 31) @@ -581,9 +599,9 @@ func (w *huffmanBitWriter) writeBlockDynamic(tokens *tokens, eof bool, input []b //fmt.Println("Reuse Size:", reuseSize, "New Size:", newSize, "log", w.logReusePenalty, "header", w.lastHeader, "re extra:", extra, "re-main", reuseSize-extra) if newSize < reuseSize { // Write the EOB we owe. + w.writeCode(w.literalEncoding.codes[endBlockMarker]) size = newSize w.lastHeader = 0 - w.writeCode(w.literalEncoding.codes[endBlockMarker]) } else { size = reuseSize } @@ -694,12 +712,15 @@ func (w *huffmanBitWriter) writeTokens(tokens []token, leCodes, oeCodes []hcode) // Write the length length := t.length() lengthCode := lengthCode(length) - //w.writeCode(lengths[lengthCode&31]) - c := lengths[lengthCode&31] - w.bits |= uint64(c.code) << (w.nbits & 63) - w.nbits += c.len - if w.nbits >= 48 { - w.writeOutBits() + if false { + w.writeCode(lengths[lengthCode&31]) + } else { + c := lengths[lengthCode&31] + w.bits |= uint64(c.code) << (w.nbits & 63) + w.nbits += c.len + if w.nbits >= 48 { + w.writeOutBits() + } } extraLengthBits := uint16(lengthExtraBits[lengthCode&31]) @@ -710,12 +731,15 @@ func (w *huffmanBitWriter) writeTokens(tokens []token, leCodes, oeCodes []hcode) // Write the offset offset := t.offset() offsetCode := offsetCode(offset) - // w.writeCode(offs[offsetCode&31]) - c = offs[offsetCode&31] - w.bits |= uint64(c.code) << (w.nbits & 63) - w.nbits += c.len - if w.nbits >= 48 { - w.writeOutBits() + if false { + w.writeCode(offs[offsetCode&31]) + } else { + c := offs[offsetCode&31] + w.bits |= uint64(c.code) << (w.nbits & 63) + w.nbits += c.len + if w.nbits >= 48 { + w.writeOutBits() + } } extraOffsetBits := uint16(offsetExtraBits[offsetCode&63]) if extraOffsetBits > 0 { @@ -784,26 +808,20 @@ func (w *huffmanBitWriter) writeBlockHuff(eof bool, input []byte) { if w.lastHeader == 0 { w.literalFreq[endBlockMarker] = 1 w.literalEncoding.generate(w.literalFreq[:numLiterals], 15) - // Figure out smallest code. - // Always use dynamic Huffman or Store - var numCodegens int // Generate codegen and codegenFrequencies, which indicates how to encode // the literalEncoding and the offsetEncoding. w.generateCodegen(numLiterals, numOffsets, w.literalEncoding, huffOffset) w.codegenEncoding.generate(w.codegenFreq[:], 7) - size, numCodegens := w.dynamicSize(w.literalEncoding, huffOffset, 0) + numCodegens := w.codegens() - // Store bytes, if we don't get a reasonable improvement. - if ssize, storable := w.storedSize(input); storable && ssize < (size+size>>4) { - w.writeStoredHeader(len(input), eof) - w.writeBytes(input) - return - } // Huffman. w.writeDynamicHeader(numLiterals, numOffsets, numCodegens, eof) w.lastHuffMan = true w.lastHeader, _ = w.headerSize() + if w.lastHeader == 0 { + panic("no header") + } } encoding := w.literalEncoding.codes[:257] @@ -838,5 +856,6 @@ func (w *huffmanBitWriter) writeBlockHuff(eof bool, input []byte) { if eof { w.writeCode(encoding[endBlockMarker]) w.lastHeader = 0 + w.lastHuffMan = false } } diff --git a/flate/huffman_code.go b/flate/huffman_code.go index 00b5c1a6cc..868c10bd2f 100644 --- a/flate/huffman_code.go +++ b/flate/huffman_code.go @@ -356,7 +356,7 @@ func (s byFreq) Less(i, j int) bool { func (s byFreq) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // histogramSize accumulates a histogram of b in h. -// An estimated size is returned. +// An estimated size in bits is returned. // Unassigned values are assigned '1' in the histogram. // len(h) must be >= 256, and h's elements must be all zeroes. func histogramSize(b []byte, h []uint16, fill bool) int { @@ -366,11 +366,13 @@ func histogramSize(b []byte, h []uint16, fill bool) int { } invTotal := 1.0 / float64(len(b)) shannon := 0.0 + single := math.Ceil(-math.Log2(invTotal)) for i, v := range h[:] { if v > 0 { n := float64(v) shannon += math.Ceil(-math.Log2(n*invTotal) * n) } else if fill { + shannon += single h[i] = 1 } } diff --git a/flate/level1.go b/flate/level1.go index fc780a41c2..11a8d4e92e 100644 --- a/flate/level1.go +++ b/flate/level1.go @@ -60,7 +60,6 @@ func (e *fastEncL1) Encode(dst *tokens, src []byte) { // nextEmit is where in src the next emitLiteral should start from. cv := load3232(src, s) - nextHash := hash(cv) for { const skipLog = 5 @@ -74,14 +73,15 @@ func (e *fastEncL1) Encode(dst *tokens, src []byte) { if nextS > sLimit { goto emitRemainder } - candidate = e.table[nextHash&tableMask] + nextHash := hash(cv) + candidate = e.table[nextHash] now := load6432(src, nextS) - e.table[nextHash&tableMask] = tableEntry{offset: s + e.cur, val: cv} + e.table[nextHash] = tableEntry{offset: s + e.cur, val: cv} nextHash = hash(uint32(now)) offset := s - (candidate.offset - e.cur) if offset < maxMatchOffset && cv == candidate.val { - e.table[nextHash&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(now)} + e.table[nextHash] = tableEntry{offset: nextS + e.cur, val: uint32(now)} break } @@ -89,14 +89,13 @@ func (e *fastEncL1) Encode(dst *tokens, src []byte) { cv = uint32(now) s = nextS nextS++ - candidate = e.table[nextHash&tableMask] + candidate = e.table[nextHash] now >>= 8 - e.table[nextHash&tableMask] = tableEntry{offset: s + e.cur, val: cv} - nextHash = hash(uint32(now)) + e.table[nextHash] = tableEntry{offset: s + e.cur, val: cv} offset = s - (candidate.offset - e.cur) if offset < maxMatchOffset && cv == candidate.val { - e.table[nextHash&tableMask] = tableEntry{offset: nextS + e.cur, val: uint32(now)} + e.table[nextHash] = tableEntry{offset: nextS + e.cur, val: uint32(now)} break } cv = uint32(now) @@ -127,7 +126,7 @@ func (e *fastEncL1) Encode(dst *tokens, src []byte) { emitLiteral(dst, src[nextEmit:s]) } - // matchToken is flate's equivalent of Snappy's emitCopy. (length,offset) + // Save the match found dst.AddMatch(uint32(l-baseMatchLength), uint32(s-t-baseMatchOffset)) s += l nextEmit = s @@ -138,7 +137,7 @@ func (e *fastEncL1) Encode(dst *tokens, src []byte) { // Index first pair after match end. if int(s+l+4) < len(src) { cv := load3232(src, s) - e.table[hash(cv)&tableMask] = tableEntry{offset: s + e.cur, val: cv} + e.table[hash(cv)] = tableEntry{offset: s + e.cur, val: cv} } goto emitRemainder } @@ -153,16 +152,15 @@ func (e *fastEncL1) Encode(dst *tokens, src []byte) { o := e.cur + s - 2 prevHash := hash(uint32(x)) prevHash2 := hash(uint32(x >> 8)) - e.table[prevHash&tableMask] = tableEntry{offset: o, val: uint32(x)} - e.table[prevHash2&tableMask] = tableEntry{offset: o + 1, val: uint32(x >> 8)} + e.table[prevHash] = tableEntry{offset: o, val: uint32(x)} + e.table[prevHash2] = tableEntry{offset: o + 1, val: uint32(x >> 8)} currHash := hash(uint32(x >> 16)) - candidate = e.table[currHash&tableMask] - e.table[currHash&tableMask] = tableEntry{offset: o + 2, val: uint32(x >> 16)} + candidate = e.table[currHash] + e.table[currHash] = tableEntry{offset: o + 2, val: uint32(x >> 16)} offset := s - (candidate.offset - e.cur) if offset > maxMatchOffset || uint32(x>>16) != candidate.val { cv = uint32(x >> 24) - nextHash = hash(cv) s++ break } diff --git a/flate/reverse_bits.go b/flate/reverse_bits.go index 09b1ade234..43a9570fdd 100644 --- a/flate/reverse_bits.go +++ b/flate/reverse_bits.go @@ -7,5 +7,5 @@ package flate import "math/bits" func reverseBits(number uint16, bitLength byte) uint16 { - return bits.Reverse16(number << uint8(16-bitLength)) + return bits.Reverse16(number << ((16 - bitLength) & 15)) } diff --git a/flate/token.go b/flate/token.go index 1c353f4dff..e0a1022ad2 100644 --- a/flate/token.go +++ b/flate/token.go @@ -7,6 +7,7 @@ package flate import ( "bytes" "encoding/binary" + "fmt" "io" "math" ) @@ -253,6 +254,14 @@ func (t *tokens) EstimatedBits() int { // AddMatch adds a match to the tokens. // This function is very sensitive to inlining and right on the border. func (t *tokens) AddMatch(xlength uint32, xoffset uint32) { + if debugDecode { + if xlength >= maxMatchLength+baseMatchLength { + panic(fmt.Errorf("invalid length: %v", xlength)) + } + if xoffset >= maxMatchOffset+baseMatchOffset { + panic(fmt.Errorf("invalid offset: %v", xoffset)) + } + } t.nLits++ lengthCode := lengthCodes1[uint8(xlength)] & 31 t.tokens[t.n] = token(matchType | xlength<>7 < uint32(len(offsetCodes)) { + return offsetCodes[(off>>7)&255] + 14 + } else { + return offsetCodes[(off>>14)&255] + 28 + } + } if off < uint32(len(offsetCodes)) { return offsetCodes[uint8(off)] } diff --git a/flate/writer_test.go b/flate/writer_test.go index 68c1dba207..cba263c525 100644 --- a/flate/writer_test.go +++ b/flate/writer_test.go @@ -48,26 +48,26 @@ func TestWriterRegression(t *testing.T) { buf := new(bytes.Buffer) fw, err := NewWriter(buf, level) if err != nil { - panic(msg + err.Error()) + t.Fatal(msg + err.Error()) } n, err := fw.Write(in) if n != len(in) { - panic(msg + "short write") + t.Fatal(msg + "short write") } if err != nil { - panic(msg + err.Error()) + t.Fatal(msg + err.Error()) } err = fw.Close() if err != nil { - panic(msg + err.Error()) + t.Fatal(msg + err.Error()) } fr1 := NewReader(buf) data2, err := ioutil.ReadAll(fr1) if err != nil { - panic(msg + err.Error()) + t.Fatal(msg + err.Error()) } if bytes.Compare(in, data2) != 0 { - panic(msg + "not equal") + t.Fatal(msg + "not equal") } // Do it again... msg = "level " + strconv.Itoa(level) + " (reset):" @@ -75,22 +75,22 @@ func TestWriterRegression(t *testing.T) { fw.Reset(buf) n, err = fw.Write(in) if n != len(in) { - panic(msg + "short write") + t.Fatal(msg + "short write") } if err != nil { - panic(msg + err.Error()) + t.Fatal(msg + err.Error()) } err = fw.Close() if err != nil { - panic(msg + err.Error()) + t.Fatal(msg + err.Error()) } fr1 = NewReader(buf) data2, err = ioutil.ReadAll(fr1) if err != nil { - panic(msg + err.Error()) + t.Fatal(msg + err.Error()) } if bytes.Compare(in, data2) != 0 { - panic(msg + "not equal") + t.Fatal(msg + "not equal") } }) } From 40e8dba1d9bf9ed3073accac0f6de6991ff05acf Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Mon, 5 Aug 2019 13:08:27 +0200 Subject: [PATCH 39/54] Fix typo in level 6. --- flate/level6.go | 6 +++--- flate/testdata/regression.zip | Bin 49754 -> 473758 bytes 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/flate/level6.go b/flate/level6.go index 1105817731..ec58b0bd33 100644 --- a/flate/level6.go +++ b/flate/level6.go @@ -79,7 +79,7 @@ func (e *fastEncL6) Encode(dst *tokens, src []byte) { cv := load6432(src, s) nextHashS := hash4x64(cv, tableBits) nextHashL := hash7(cv, tableBits) - // Repeat MUST be > 1 and within rabge + // Repeat MUST be > 1 and within range repeat := int32(1) for { @@ -149,10 +149,10 @@ func (e *fastEncL6) Encode(dst *tokens, src []byte) { eLong := &e.bTable[nextHashL&tableMask] eLong.Cur, eLong.Prev = tableEntry{offset: nextS + e.cur, val: uint32(next)}, eLong.Cur - // Check repeat at s + // Check repeat at s + repOff const repOff = 1 t2 := s - repeat + repOff - if load3232(src, t2) == uint32(cv>>(9*repOff)) { + if load3232(src, t2) == uint32(cv>>(8*repOff)) { ml := e.matchlen(s+4+repOff, t2+4, src) + 4 if ml > l { t = t2 diff --git a/flate/testdata/regression.zip b/flate/testdata/regression.zip index 4daeb5e468819035d9c86f1f1b5fc44fb911a62b..3a1b48a10f4cc37c711f74be8a76c2863061e072 100644 GIT binary patch delta 427385 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
es(Y7(| zXmvp{CX}>7x4T>*k&&ob3f)%LRO{zp@xCY)`Jiyi zc3Mo4LLU>OsfvIJ^L4aVs zO!2H`X%3|?vZ8lxQ_Pn(J}_-APOgjI2>dyCbi{(2{Hp^+qUpA;0VX>vd-O{=)<=b}wwfQ!B7P(IQ(pmNy!vfaUYTbWmBD<@WSgz}=Kp88M@+Jmypd1y@!Xn&Z? zRHBdxPI)o;^oel{DHaV1%E-0MBCx0|$YKrJIkv0*PP;RxHl-|w`h~)wDJ{x`E;q47 z0HF}c;+8WQO1<6Ac5P>wMj>*7X+c*^Iip?Z_IPEMVHGAUV~$V)otzT^DrXl4-=IU` zayiJ-?sd@JRCc7Jvj7)VvK>^`;D6GbUY1@02ivLaWP0t9os2nynZbs3C(14V_xk_o zMgM6-M!V2Ae!amQFZfNCN%&nV%F%~8_7EO}j0Q3@T}mUCO%qgFrh6JgGyd3R0@_{1 zDd4p$K(wp3ac8vB{N#bC?;K7!=<<5(j$lNrA#sr8OpcQo_XUGWD`Ur&ruI)kQ;ETfbr$Q#P93UJeDVdzdp{PB_{*xGUR5Wc)&|7xiaoEcn z0BR!_nh4X_xin4~T1;0of8JQ!tc54Bj9)tf@hk+0Sb<{)G3Epv$Y3&~ z8Q=+}@v`tjxPMWT8swbhP3fdUsG@xYLtWyc?q!vf;C5UpS7NVZshj{jJO0=i{M-(e z*}@bOWM4&xc?@Kt&=+mei)=d7lYW|$^5Q75%T9Yf@)Egh$YNvLi~G&fZXLs zX5vrLyMN@&V3`ee1e1fxoI!`$FobZ94kzeeHh*SV*qE7>oh9m?k`;A{$S3oiPCKPu zb~>5v5Z@A6G6H5=22JtWGwqV>QV_=AgQJg=Q6F%F-8)!P=td@4d(?$+6XXa7T-d8{ zE+=qP_#MBN>BmqK7j zkzL_C#zdsK;EN!;9b|D55`(TBF2plIIg)(Ah$Ya>V15~aAUHKCXim0(tRzKtuAm=JVSE2jMbg|5Ud1&(vdvY+J^El;NX|8YEVS+dm4-a%}L5;X*S*j;uP@+Etn9y{l1 zk(r!BtnPGRPsyB*^Mo{7&UZMS67pfs@#JU9!8ArZz!CzLRG~#JxZpO9b9lYTzJG%Z znM|iu4=NqZ2Tvs1oh?8z9+U?69m!5a3nIf-eQF~iAf|CJT(W}zf}~~Ulc-`(Y3Tpt zAi^cpu)7>iFO-e~yaOtBVnQlgdAM+1XAtu^I0yL~`o;s^bfkH`$h{{A*-N%VlN6{> z5{Us30>^nIPtc2!IZ3OWMVOoB3x9^2LT1SFc4`9j%3us=iYLfA%t09mZ`*@P z11EC6oQxC`D|wjTy}M^c;^ZQa>{uvsK8FJ+#dbVJK03u@nO~_opfypDe{2UgH4|og`i1JBG02#7PcCJ4nK-$2-X@3+oXh(Jdcxkkn z*PaGsxi|;F1G2IQb4rL2(n!*i?KzHQdy!n^l3+(U$S)ci(w^TR`_$y!K?`Oz%h}2b~Y)zy^sE9TFRcItB^cm@J2O znw-oru5fY-Wt5oA&*0# zup_5*+_z^sGhI&DY&kQ0I7lYw1nounM8gz|lfzk*c`p~qUSvPwaNx`ddR##z;c7o7 zqlK|5C?6>)$$ueZhz0Q;M})w>yGc|<4&H-$qhGPhTA884foj89^QC2iMw|{0;f559 zBh8f4i1DcRh{I)SVS=}8rz01dmDZA?JAWlO>F`lYhFQFYM0#p!%Zjh{!`akCP10 zn@m}Ox$$_34~PgIEG#<-q+$$%iU_gq9iclN{hgU>9O`+@VUKu?Orn8^M?poXZGYk(C-QCgl4nKw5jW>42?ld$H54U>uwNw8 zPLiGZ$(fFP4%Fa2<+5bANC(z=+oyqMjT(Sz-V&U#50UCUyeA zW#-(m7cS3E3XHIU6))p=XEKd~b_Bg-SL|fAXja(JWS{|6&Gb+olQ!hg_xH$VA^rAR zK}Pr-X&1*}ft9e^?wMHGgOYM2Q$PsegFJ{7B%9@8-yCr`^TEnE+PSbpha(>!TtSbQ zB7cM3p)-L?M)e4ZwK&2Dj)6e5hln>wqLSv3nVX9wVh<6e2haxEsHLcT0DUiQGui23 zRtKGu#?fH}FY;N7QQ>nuq-{=WLE`ONObbcRIr>i-F4%6+9Olb!K@OTYHj^lr1e*gX z5Phck=(}h+IPN189tmYi!^Z7iFR0l=D}Uh_tcGjlNi0fjEBL&$33{Sf1%{F|OOCFLGWmhwMivj5DqrTkx-He#}rlzW-Bkf;5Z zc}@wPeNv)Rm}uS|^(p_REiQvf&eDGH=$-aAtG@4rlm{)h0k&|f-l(rb-op0i2{O-GvK5^x-<=^-G zZr6fp!)EWQ_fG8R-G5?_#5b>1WohG^pKh4@`HpwqI?}jg$C~3^4is!#T-?9o{$&Z< zdcN%He0%z?UX4mde4oGXyDh!OR;d1g@zYOMu6V+E;ML$Uv-RNCQ8fedi8qbJn)&@mK$F7N487{K(*$i)Xg2aP-Et(%1P}`>P}z z8>$&Ee&XM>!+(2S=IAN$x(|AO`kw!#Ve1=opR@Y#^*-yOJ+pVsd+R-`xmVG9)mA#* zT<~*)qgN)ncf7K)>)M~D4DzL|{`8ritz0b<)=WD$_82dlgD1O4yk_Xp&jFHwd;_T(fH^ikL6_#+<*Mi&cQvhp4#;E3tOHlN%$ds z^rJnc?c2S<*;9^oukwRu{#keGmy2ui4}S6O+DG=i|Hhee-vln%x-7Kb@1<6^ZQ!)R%yO!-BRwesNSg$t{k;?F+SF+V|ByFQ#ZZbA!c5?7k>Ka@#PIK zbbYCQ?Q6OPuYQn}^-|NslPTO}-w(UHF24Qpi+}o4Te|fhxTNnhE0Shhn_07HMi=k0 z&)SV0`|*rNE1i0Fgm`Y&p!q9JX>Vsv=rCwS`WtaCef!#5`g%L74>-T={loo#pODw@ z*4wwX?yNs!W3a<#mp?x|=eyC>Mw#Vtea~FExu;up=?Z?=3jgrRTOJwNuWjW~RSj>o zet)F2!kW*PEM9W;mBwbho_>#x862mf-@{_oM{t>7H5(-~nZc-&%=FtB1S9<-Ar3^S zF>qE+ltjU*Hydq6J^k7Y!6IoTJ?7DyBmrcklS~>x<%W zvWYr6jYJkk8LTt%qR!0ez@KKpDA8|^5`Q_Pk>kPVCWDOw+wcY}XR?WUz22-hTD2OB zL8Aw)(O>*;!aoLH$JqoOF$8DTOIE930k0uEE&T=~gVAUO-GT26CeADwIHOH4a5e*Y z4LoGhm^2cmwP-COFM&g}L}R?kWT9UIq!Vm9y`Hs(MPo8pB%{e<5q0!C2Q-4-Vt>-` zCf;f?f#-On%c|FlT23PxdGMgoV76F!E4E{>3X%lY(;{A8uQ8JPndp}gX!Uw6vTf#U zHUubfI%E|b$U{af8mrcZblQwW$vltAIG#f+?C;Rm3VKL~*<_OhGmo8`IW0I;(%ZCJ zn^7|IR*dAan8XVPgIULmdin(eynm5?5061K@fxj7s}r?)o_?VS$0N%;XVO9vc%99} z(=Soh(Y!{BR?8t5R+~+0&_Zgo5^u0+1S6_#H1VRr0)+57Nz|AqQ5vJ5*K>j-X!TZ| zUNVSAqlBtR7NgZ@(MXccVzB9XNixvi{;xBNR>4ZYb4ug{qrphO9!qaE+kZg(sD@+( z1OXY5elM6thX^H$#e`6_Hr{H6G8^<}9`MlV=vQCpY!<6Vhh0GSc#*g2bS5*hBGE4v zkhDDWB%qTp0JHiPWfet7NB1K`fv@}Z12tupMEC2LRYa~Px1=ZwI$Z<I(M8eNLJ`*N z7mcMcDQ-8*rLV9eoLyECl(Jv+@~on{bF+$Q8oZHJR8o{%go)>7u?2D|K6+$sbO|mH z61|+eoaQ4F(yS;8ghavh10nUx>X${-g?ES1bciFmA3pTUqN;*AihrVU0Kkm&?_6ph zOiyltRxZiPDk&LOR0N(c$znE_a#j+Z1@X+v&C2Rml0|c6^&^V{uw#SKxJ9glC?PII z(V%u>r7R#jx@&Y+3D(KYMbb%;kt3`^XPbUmz;RI#a*w_Aqunk4F>7uKwpf$}X`Guy z4m~<_mo1u$Jr~V|Uw?)vp1adFW7k;QhQ78bPqr;+oVcXg9$bJuE~7jy$1q`saGoG&;P+Yw*LAzK0oro;QrTU9252~ed5@j3Ef%=6KeV= z6ij~TVUN`L+I=e)-`9VAvnCVfRjxkaus`de1)p`^*5>^`oPWo92gInM4;H`s%JC)Q zlG7h|vwqKyY~6bNkgs2O@{ci(z4EMKL*@(BT37si;#te$q4TyCzY&#|)o$R4L$xwX<;YLjU8_o35LGd03}0H_x2f5Z%60@VG8x&VctuByMeS zq~!z4#JbI&TYs{pwRLE|z0b=Z1?IMIJbsQXY2NAyhqgbGSM!_WyE+cK-7$4?1G)Dr zO^40W99i0;-?CMmEjxw;s>Q4`Zu;=xZyoZNPe}XZrzrl}iS4FH(@I zzBZ$J_xHLz`oZF_-W)yW*4SMgyJttYf6#aS*TPS~AAcrSer0Fz$HjfO^pCkLH5+>2 zO;6=FI&B@;?cL_*qEkoq=(KE3yIV=sS3j|5L&B(!T3KJ7ak2ZD2`&3&eeCbmV#eFu zKaYR%%LA{C|6~WGs!spFp{ zNq?@FYre8Ob5&`B(%d20JvKIdthinvadyorUw^z??Oe4vm$sgtd7|Ew+m8-8pR&8h zg4~6_zVzkb(e=`Q-RClIF*X|1D80!iPsUuTef~kij?dp;aQaf;#jf32q0QOGeJ0NO zWNJa5hiWalX4-aW<|94EZ8B8%rzg*;f4b+FyW72U_WAx#)ld88*aH*${xH64rGclf zR(}fA`>01u)frE&9sfen@9$mSG5ut{+Yc^mI6iOu3+)$(@hLw~dMT~*xs~nqt#y69 zqH(2Z6Q{TRBICD9mM(A3+Of5$V%)aq&&=5 z{?hfG*_l@tJlLb+B3qBfY0pop;p~0jyWf`-$CZw%`-%Ujb^QlNd!EtVczWx1Lx1OQ zcz;sy)O%h^q3#qjym9C8gN>$0?=cY}`kt#OdKr0#B>T=$Dxwv8T+T{Qoyy4P-rM+( zE|ADy0wswqcZXbPxf`K_Lih%=ROP4nOL^{ChI)Q#=t%L8Vr6;wMl^TXdw25A1P^}-hYpNIG_FIC|G7iWyP z?#4LkMLdH~io1m$oT}9OKVM66mf@EQy+VW8tB_i%Z<4#d*F1M&sDCexkbiOgAIZXf-(LSEuKyC( ze~IhA#Pz>&{g=4@U%dV?aet-kUT|~3&6^1~6B^w}0e8{;fsJBPQt*cc+yn-{UXU{X z=I-5%5^kpK-hC5ingpC_0LvQ*2|PQu&>rLB@I@Tmr}gQ62D?8z7gQA`N5#c?y>7O8 zoSYHmj&dKoGiLONobE=KMa9N}4cziJ@Xs8^mFelnD0VrW4roMlrGH)kNkF#0h2qk^ zCFye#N@YP##}?(`avXKJJ2x)9)GL=JFi5dOop)P;0x9pA3|V2{gg7@LA;DXk9<@!T zQ9=~OCuGRmda&O7O~yQN337%kXC!Qks!miDH(V|)C0Z|aM`gIZrPZi650>KTgQdCd zR~XI637g!qq6uu;tt>g}l^}m_OOR!mo`|c@?#y8kD(c|PO;=c83R8!Wf_SL_cxd=G zL79b7PF%X25EmCm8FwQr#zhne4TzIV=?IKmGb)aYD@~76N-vi(nhbMCdPZE2I3cRJ z8sE0KgG7fJamO-1hjQ~M+)FG?_ohdcMhVqf+0q5s4LU51Qs^-5ATNK*taS$prSfoM z)J<_yOlw*814gs$Oi=W^ep>{a!!ipI%af$9?{)2xVHNv4;OMG{aNHz(cg z&iySRBO|@k{neCh;NyhSj5xP4E!*bs>T%VD;pwH+=!`<+=0v44{h%F0DI??3kz2Q% zkU=k^X{Uz;!?vh|(%;}5O4HqP2I&_)R$4udFO6cEp%y$yyL0Da ziG(=|d*g=J0N~1!u?RLjkC#}Vf;^NX%Wp?{iLfyRRxB7pnucjK02C6lAD=LP?PvH$3-PdQ=p^UoXyE>ISNh zZQF*s?m2S0P(58~HENgP841yOf29xQQYk+vDLN@HDJdy$>K%E)-LhLrNukG*rrvqs z;b>J*lJ0*#f+?XYE?6#UV)Vp`N%UAGJ5v-Y^|ANZpXi8fQIvEs9f@2$FPcUw&qS-A z*`DZjUQ%>9tA!EKNfB3w0+QG>>Kb{Gdq`s6=7dcNuYwr}-bqQMajFAn%dzo!Nk43n|-D zQ$jZgLUg~b`hfauI7QJBSzz}Rv18`=jy9yH?qIY#rU=z5lp2HsWS}1ETZk*tGbEL5 zg6&F)Nn!C&l+hEZp7bI-c~Yo^c_dY#jfeJL76bzV{<*`aC54CNLkP;V(*u8A2zSqhq=)Ui@+?tlMA?)%^Fl2Ut@g}B zg)aVuYAf;FX|4WpB_*i`8?2L;qzq*k40U_n9kHt+)J+QGH7SV^LQ-A?phE_(JO#5y zB7J*wS*b(oh09qM0xd=cZemh&s7vYN#Ji~;dy?6%2pe5CG1}lRwkx`Bv`RL2*e-w8 z$p6|Mho=uEMSXP_l?-Jynw7b{u~E`=Pa&p%NS>4hFu}xfyjWgB)b0u4QsFE5XQILl zc}nH-rm~sBj}kYB353Rm4?K4{@32Zy!owxT>#{CVaILZ@=2h4;Ivh`kk?2LaE{fcA zQqtYr2L}Cp`6&MAknlt&O-xFPSoVJou&MdPVCJi-=JZ_1`qHMNS*j=IsW8Wjknv?N zbykzvU^HrYh%V2wA0#siR*glpXe^x0Y_nO-bZRnLbr!8o#2wx!S_HkuCI|-6YT*Py z;7t~r)+CrjonB*>Fh;MjSgksfjlQX3vT6jaRp1O-lk&s6W~oBQa&l^oVucIHT)!A%IV8Ojd(nwMrak(ijC^ zv{-b4!D2B;CV{sZO_;{OW5*gB&+}T2H<-0H8}?y9xLUo&q!o=itHFO@khBI7<&*>q zmeT7bPQwW{4KHeW)J3#PX5MTtYqUB}K&7>a8skL+Z&&5|YRZdYz!Na-3kX8jV^} zu$eK$sFg&m*=j)qI-P$`vKX`|32(OPv_`YhXfq+^b*f$5V(%h znGC$iXwyl84k77mT6}5dHAae<(;7_{vjMO)m}~}{NdhKp291BtY>|wjq&HbC1R*Ud z!<(&I0f3+%Wz-40Q773(16LLM-41`l$?INk>?B=jZwlTtR|E|!|_)78F&pk z(`2%tMx5Tt@#q(=)A;gs zLc7@vdQP&6fCcKNLoOr@76nTf4O;p?07MRO5)Bd_mb9P~9SWy2nmDt~iZo&#U|Q1A z&xq=*Jg7`#uyGo*Mh89;IBXeHn6aMOA_8tYyQEf8R^ zS+%@bG@5_W>1LgPq*yE_6b31@2qxf}@UPP%#Wu;paT=Z)M=K&04(+JLD}X}KX*J*m zbb;AwL^{njL5pD~#3NeJ+Nd-#3%v4rG@cbz=6PTO-7fMV5uUSgI^F~{S~;^sK-Opt zW}_K&X)uC9@ug_SnnnY*phdxWOkn`FOsE{l9?X9!=^52xC_>j8v>Gf992gB&5ljel z2znDwKf{YXf-22=iw!|bIx`?;)d|>_poL!G8Eiu*nDk(5&Sn(=X+4TdpG-zUGT<5d z!Dm6zqpVi&fDy!Hm_Y-vLNPQN$fE`+wc6;%pP`1BPBNHyt)S&B zS`Z1^(W>KZf=1v$$bhX0!epZ#?FAAzBN%@Hl%TijLH9ZfN^b)}*m#QuEFgkfP)Z~E zKo3RL=*>FG1Ym;Tw7A1zM;5aV`2l-Er36r-(TGG?0b?FCgOLU;f`vS3Fq26Xz)-x- zpb@})f)QM(lQdLF6BwG4G(=du84wU96LQVb&mN;-22Lj-4ICt zwONoet44!RkVW7e@ZceQpf8;uK%n6h=tsR#BLTFJy@4lJ$%2O1@v#Uy}wB?-c+1IxF{V7 z9hgs&$PA$nl4!9~lJrm{u#C=Vun6cfgO=w_5-EXT0U=8|><;Xu5p^Q{$T@$21Nj8v z(99qR&?lr0nKv6@0SpGg2wsNq(9$~aDioDg2fT?o2{uU!QYH7H(N8dR4dfZiW2HX3uolXo`AYFW?qmiMxzD5Ks~fpE21Iq0eyk-F`Ch)R%G33 z)QgmLQZqOyz0Sa)a#lz-+6sR>2lu40(vPm=G0p%;qs|6&1^pDg0qcdXl7wclaG24c!Gl^j9FZr+*T5AR%{;Pg)0#!tFb&p+P?&5M4qPFCMnL{> z4Q4C$Xo6Y0d9M56Dp|F9F4^4W$d*A(1{I}`u3lmGs;pj{es+DO9k+k|;Va%ah9Bj0E z!^IP(`!vtio<4ErjFhpa1J>D(KmOu={l=^$~lp*ejj86)eno{EbB;sPvXVRB9+;R`byC`z@7@-V9Dc0n;k9y`rQ;@bGQTX-`|n)z(Ycyy-+fy;a`mC4S7)t$eDGUe6-s(JC`lE-Q&!A)=4J@9{h;gu_|`E_p#N% z%ZqdCO^aF^yTQ3@1T!}UbN)iJYDkBt0D(#>tq zb7R6oPkr`S!y%1+9=4)|Y0~OzuYC6Ft>?CivDzosR;Yh7==1db3v-7}NRfw~b4<5% zc&n>$;Nh0X>%EcY^%bmK`P}OlE01V&X2iFfZlrDQk@;<_w=TW>Qo3-Vdv$YS)W+rQ zYft*|hYs7f#$LYIczV0rMQe8MZfnzMi&MAs-|_1b@W*M*&3e$W@bS~$MjOI6?Ac>Rg@ zyHssDUmAAfc-z82%uUOLjmO*9T2}q$2a~PdUK)ozZdLuat;?1!Sv&H_!+&a@e0oP0 zV?o0&8V-m%ySTX=_rte)zc^OodX)})rS~%@4Su20?_HvMnm@W&!kt+(vB&wt2j(B| zwP=6Lj=`Nhw|h0KkoI`NZ|M^+e^PP%bD#hC>ggFx(&|@TTj}?sNmb|WopN|{#a0i$ z`>2p=Jm>gy|A5)2SB-mQL+#0j>baMu7W?vFdgG-ZmX2Oq>Augm#Lk+xv5M=HV9c`D zF8W{FdH$UvpE=);+V@DpQ@7^ur>@^OX}>P#s(&i>^q^&wVEHUDSiIn*S@ZgPPv@h;=q6Q z^(nj7y)~mw_d$iH$K@SbDu0@|>+nyz_8xp^(b)!Fd4yr?iQ!jc1DP^Q_-m z+HB3bC$hJ6*x&z$!ah~Fp<6y`JEC{VybrgI_dNBLzDS?jV^o{NRUbStd*1bBL*i=3 zA79z~cGDK;vj@LktMXFQcXJv)1=Cxa7^B^y)q2ooS1geLZQ^ zYg4_4_8B$jbRA!PZN~MP`?pmbU_9tQ$vw~}?sDpsbEDt>;hCTiR-y}Q%rySi1x?5 zD+}MN(ciGYqOS}8YU-c?&;M@S^ycJ=?VOD#jO+ciJ>$UVr+)ruVZ9||j~(vgn3CD0 zPKU*ov~RA}`}4DnsqKmxH_bVPV=lM=ff7g9(XpieI$2*~)```FqOYFIZ_;e)_L)bg4Ele(()TwySKojB z%FfN1d*5m>^R+6T^*MRnY92TJR8(Oz-|I%~aY;WeX~vtE_T={3_Szd;_w=bb>fFXv z*+Y9@&7ab^yJxb?KV$Z%ZIVA~erdt@6*G?gG2Ff7(bqFx=+gg#uY=KpW>&jD(eTiQ z<1Z$SpK-%+X+)n@?_Ga5yJ1A>;}u?f_P)8To|!WGObgGYw;z6EUetRzM+XcVb@RZQ zHS(n5{y77me7~V7uV%YRqh{P(RX1wJUg6OBrMdMtH?+>U_Qs;z8=f_L6S8W@BxIk8 z`|ZM_u9tkL3oq1|Ub~B7$LQ-*-+E>K7Y!D5ytL!gYT@ZpHZGB%d(O}@4D^J6m#`%eF}Uhbj36{iPG){FgKpAxmB%B^mXa=m5O znxAWrmCvl#Kae`$n}H|u2fpxoRol65V#YtzHLz%Og9ZnRmv=e#S+U7C$yZ-}W%8|YyPk})w>>bYb(Irm zYGr?Vb)Wm?{xg!^{b60j8NaSwHR|bOo%+9X--OD&I#sB=W7`|A?)s`pg((BhzL|XX z`up{!KRAEu&=7gl(&ze5vv!;H<$}iR>Qs9^uHw1BvDc!Qz1iyQV3;?gp{#_GDjq1Qc;~sM9*G(?{gd)?)&@w zeV*s{&u85^=j^rDy4E$mulIVr?nj%lKYZwJ3Ot{xJTbIp#w{dc{6fa_>5Gp;UaEMk zY-E4Ff4{PFnEPfzR@APni_Ya{CxMD;_+@9&En^QWy(Y@lPf6BKlUq)E` zokNP7+=oK8b$7;agrq72Ca)3`J<+6mvts_v?Q;LEvxls|T#FT^JyCKij5WT>xAs`J zmuzjfwQIp@>ZTIUL{U}dtQbM6R?Nq#nw5VCzYH8IJkR30dgmGAdWGe@TVl^M>Kkn+ zPhPjKSBUoRJge-N`(O5my}i=2vm|YQ_1Q^G)>KkWhWFOcpN4hpA6J;G+nw1d&dH3= zOSsmhFI! zC;U2w?yS91Eirki>B1hnd=}R0m2Ulm8&jGKLuHn46wqNlyVqZPWmC?nJRx7^SS$Qa z4(v07i{;I>g-PeEMC$P^^@L=lPs&#}@;<-{KU7GbEBVIU(SLF^ZxaQt_LUrPmsn^|XnQzPf+5bu_;|#xjeasb}?1l?xH;j<>C6FWSJGByJ<{ z$-SGKCc!FkZg<^IqsyPOj^_9g^oG4mpR?6xN-L|KESYkOUDb;c-ys?X-7dRMju-bh z`6ibTuJ4t8y?3LBGuBF;=(?LYaIVn!>WCF78P0b%J=&64mSnTlV`^C6@WFqJ+vb8i zikOW;iqGdpFZ5X65u-ZMFg-dtnx2Wx4VzS{3AwS{N%u^OZ-bbKM)LL0hG4&q*xP$ije?Lyc3t@HfZ`s~X+A-lpXeRx-ms~L%bu)hmUy;sCCcdl|bFaNQX^&g4(-BI3+fEyEN{*ql-9WL5 z*2-Hf@{muR`X1w_>*JI2(CmC$7G&XJSJbF<;H0XXZu*=z(k3{asUe&Cc~i`Nc_v(?S`eze}Ll6CqYJ5qg=A?M@wO z3)rE4edijza1uwc_K97lhX2b6v-X?BmUC}Nz~>EwQ$-}G^xoU&&&fNBcdI&eaR2)4 z$uW)hQ>l6k0;PW}F4)t~c<3<)Gfod2@{~+UhhbU`E)d+ zb|7%Ti4vpCcw=6CpYkjAF5TN7^|ribQA@;@M9K!ph&`pJ73|78?aZJ^bJ9DbNNZd8 zxpnp5zs7%eh)PtddCjQb=}VGf zpIVMK#-%aj;Op#8F}{R!=5UwmRcZfx4R_4*!c1JvWwYc8?ZT~d?c zTSI?iRFjKSFL)DGqdndbbT)cyTy$fu98c~Kn8haX`@XX%tRhBsuRSd!-~|zxGF?k5 z@sf(|!wL~?v>&Zb{n`;L8Enn8MdNy10^h2pdt5YJuHYZgLgF%=^Zv{39*omr%4g5Z ztSMU34_k7NN((-XNONf{F^YJWUZ2#WVpxBu@ZRZ_*sh2*%N;g9w!0HNZ@Fpjn=?u( zLdF-?G={NEKi2!?)&^f2Vm4ekKl8z+{!j} ztY1HN$KriuI@ez-Zrp9Nq9DuHm^T}5#75Z7bg8p`MBZvudhaapZZW^5bnD9MRol(d zPh!`7i|pu;v3^QFE<0=@%qFeY7NtlS_HN5|O1#cc#Qk3GeIu<$75~AjPPRiC{L@>! z^)~+08)p&U-CuDntMu;thru<`W~YBS$6iWIpRoJHy6z>DQ^WPS{JL!yHd$i*7%nsL z#*r>L_oh#@+?}>RaGl1Tqy3u)yI0R%v+L3(YhLGWxx`_p5f$;nc8YVuUa@MwW8r62 z_EGKouwSRTe{H;wL&XPMzx++^JG?qZWZ4-T-mU9NHb0@kdp%zzd`E2dcng0|6`oH2 z<T3?>x6@%bqF8XwmU>@nGx5y=~Pg8%F0+{j@I2#EeR@ z76-dijS98-E+B!Bzx2KHkx0AOHx#84Xu9ySds|ZH3X3t{==im7`dW;{^C~wh1uP|J z%9{l>n9IuggRV+7o+x7_qX+L@%KLV+4xh`%)SOpmZdJ| z?N6Z{L!QqZG)x%nC32g8T;@KP(D;gfkh?ghslkl(_0fBu76aRSyoP^P1f%XF4Wr#+ zJI5Vs#U%3XZ_{gZ73o|d@gx-&(_6jl(QaqcA8B{w(%dfXw-{n%(RCPlv?DsaWacHKuVz#G!z1$)FLf| zQVaTmZZR@YpNvB_i$mAbz#9OUfS1$2>Mhs>84nAAN*FxSpns&-@QDyDBu&LpDhDyh zII=zPfKm!^3&}fBTZ0%RL`YC;0H=gN6MceMmW(?pAa22A3hRH-1EJ8!KwS-;1B^MY z8#EKJ5F&;$zu5=|l;4z&bC)4oK+-Rcw1H;nUKyx_7fA)thdMlIu?&ST;?R#cvJj}R zErc314T6BhM$(`k5YLraI*>ptS{xU*L_@M}aDu?u3GNxBQ{zad(=6RbR%yW)XaK+l zFVTp|07L@!9H@Wy#K}PS1oG}UN>vu}iQ)ly?52G9eSjCfp!N=O2OuIKk1)9eZ=Dtb z1`Vhq!uSI^BD|h#L7WUqk3lvVLZl-cM)ieei4-VAxBF=*bm)eZ1auqeJuuw{ovvgLAgG%K^<@BO0(=*kK%#x5P$3*w9=u)@D!6@vrUpI3+!NFhiVksP zIWV0@`w5v!L&O+Te8E6K4IxrQsspeA=0}h?cnydT)O`>p41xm69i`sz`G{DUeIb#6 zS|-ebVE%uH@Bp|EA@`OEnA||ohS&%S)qvlDvmrC&A*O_aoQw<<2hcU(Cvc4bWDtG< zH*^^3q0R%t3e*G_&;hVta5q_WJjKlcrk+q61!kb`0h|@;rScG=1P72H32_)HL!mj) zBteG4hk=d;Iut~>K=*~3FG9E=#t8Bm?Rc!w}B5M}uE zlb|4>W#HI;a5I6Ag^?X%GXO050iQ_SwBVFfM_vgrtOMKTlSM2mle4+sz`cVR|_zzU#f2vLQv3GgiewGZ4a z5c@LF+<>?t@D76R08GOn+6?`KDK3f0fp9^1h?EC}X@Ca8G;B$$kOv$GI*&w_fMh@b zfyZD12xuv6BgiQP`e3#IS^+EuKpr5C@Wll(ZGq>35W?sSNd`O$X?OtbkE3h=lofxd zc2F*mvjEjAf~lT7Adqk#%Rn^-G(#{SgjEcS7(C#*z~j>Zyg<|;kHdb4kq4^i3X-|Oaqsa#?f?fdRA-7-Pc}P9bq!H4xfKsNyS0yKrzK@|Zi z2!=;0)N9a6_}&8o2@ntf1|X2Iw-y>7v=WXP2pQrWMhX%gSA;&Oqb$N70W^OI&%m5K zIIa|>;lM-p8^os|cM_pe$RIZmfIa{vQRWXt$q@89C`Ca7fE*6* zON8=c8pIa5{~)Xclp}wROK}5w6Fju5c0pds#4sYxQ$3bfm zj-n151>ZJc5Y+>41&M(2hYZs+NGgGS5VFdHkRV(HL=c)JBN=Ev(Drc9Yb_wf;z0L+ z^8-SdaFk#{h2D%1ENE*#K$r}9psWR02oMHN4I}~NWz*P}5blUV=oMZgGs062on0j7CCOLe3ikivz`h9O-Zf^--zNNIqO8_-*UqzQEt zE0jSkB+ARHqm&P#Fra?{L2X+;!h5;3N$cBH$!fg{w>GY?OWj^`^-AKo2U{yES*``Y z67xIp@rjS7j>Vw;TaBX;^n0G|o4txplqnK!3ZvP$;ZdX1o+BITDzvbQJlN)x>z&Sk*-vGd(81uG8y`hcWd@sQ#c^BmM(u;dZ!z;s&bD|VuDvs z$Vv|8dD)V8PoGEc*!8(8c5{P7)v#S;LD%%L`13mPhW+n0au@HyjuK7LB;OH`rM`?l4gEXSU{qOywK_1OQ&NxEr^Z?USzeoWxq_XXIwv#kGeRohC3? ze&#CP)4wWEr?KqRmv*N7<5)j~t;=6AA7rv+yme|oUsr$nYxTuzRP;Y^x)gI-V%Bz zLiuU7>{nNec;ZQ~8-ZclaHmgT@Thg%k(sFm9@SU-a3Pj+N7jmr^?Kwuz5HRv`)O-b zS(e5`d<}nnvRmR&E&YJphoh4`AMbn#_t(FBLAc6wrsgXr zA{(_q*TZ>v7fIV$9mO3s!zl`7mxg580$ee~8y49Qb62>(^?$s4&$Nq=7xvh&#Eu_q zj@H+rur&G~f4V&G88~$*N9xdEnQ7dfFBqiH)@G24`^^0=yp>_v;4hsc^hFJm^L@ z9rxjhb=B;Hrpm8l5uS?P8%Dag58az*>R-*7sZ?HkwWViRbWotUsf4QIMKsM0hbLlf z?mk7VI#NH6dE<2F-XE>mr=Ys=HR0IKH)nsH^y7aN*2#`=uPrEF)^D21b0FRGT&-4N zrn_>}eAV;5mO;~wrXA8^8+%ums?X0WI|^}Ed=`G&9Tr&SjwL>4776AP{h)SqN4n_7 zn4K;X*}>}S6PttDeSE%3?MSx2WhGf)QIkEH|LJ2aci%-V_(tpNFtHtsQDUA3GNQIBX9qPbNHeNod@Cl=Tq`k_a=TX^ZzJf z{z-c0y9g2O6X)4Hf}2w|TXCB)pALVqda+ZBrQ5*4pT6^QPt#z!omD}Sc;$&{TBZ#a z-dA<;m5ss=H_O^-w^Z%nc#(or@m+WK@rRG%!R9|t{~Vqw6my;S;#Em9X7zNwA=@=` zxO!7w1poHJJO*Fx^)*4e8TQP3(5o2G9O8O!RP$3r=)RV)mGnKY?+*9a1`K~f4A17d zpBI-5I#tdvSnCkkdiH{3ss_z%`-^T;XA@toXwZn43)()NM-+LaCZbLi5vbslyx-sB z?oPo+TkyQc_-UNBp1HU~=3(7jkt5MKNnt=V;(kuiGWQ!UX)y|0G0(0f&dhizKI(fQ zM=jIxI_Yx@e*|-M+?K4XKlgvGGSVr~;HciVy|||Sj+Nu<>xWAl{Ikn1*gk6V7I|iw z6GvKpdJ7{bwYozAPVBUuoi9|ot>md2%;hZL_t5n8Ih5Hz$%KNA3%CU8<=xPf;x?aycUUc?#M_IZCZa)NAMo7vNIB- z0Q7($1DFOW00D4hBp`;7kqG%&>?fF*keM+$xqxr|LFQkAzW6z8(PohLl2K56B$WNO zux7)YoZhST1~$U4CE7maDkn{knV7~$R;x(8`99oa|9R*-T|`%?)qLyQ0#Em~w>1fO zcc*f)Zhx64(ZZ{#@F0Ivc+dRy(Z;XLJf}D5MD$upUXs`(DVJ<_`xA!WcgyBNh4)*I zDodzH-*J2Fy57u3C5MeEQ^IdZuRZ1M^V1)@gJ?4vUo)7tIup&>xVm?@`-yhm-N}4f zccQfB*^yw07p5ER-wEltPESYPj6Nz%=fU#&joJ(oJ^%LZXQh8RLO%`})R`1&$g|B> zFl};KUBS|NV{YQ%jnflS#}9nHc(!<~=CrS5XfMs)tva7IbguHvaK2OBpVzdL>QL@^ zb;0F2u3BSZrez9aLRUMjb|f1zOsSOWZ&Fq;Kj|b>8CHG2S=ix#qq3e7&ZbzeS;P5m zvwLoU>}7uQO6q@>?~*rLwXnsiP6I`aUDxb~6IS*pxw)CuVH-0OqRI?)B!8X|Ki7ZfbZ5OGEN-1A1Eaj-OP`S7ruIK7a}oDby%-NyzGOP$WKk*Q$$ z7|hXa_DX*cM>DK%svFn!_`b#K%_1+^w01u7Z*g`WRQB6A7U3v(-L+XUWzH&pWB5rb z0X`FJo+F74-}cD|u-*--H@;J#;m_uGNieIN_Qq(OgtK?G8SJx@!5gIckJ~`73&-RBCTu=ae`up730? zHvd%9o&H&zN&L?lu^QS*LfS|pE&U_D*qw!G2Yb8&<3Bm_99wa0i!*0e!jx9PE15CQ z!Zv^7{3(s`g7ly3kM-3pKfL9M@u_F)tjjog&cxPFUzJx~yNi4C{q(hsH`zUc-Hz=T zDV$fP*B6##{<_UBDoU2)siw+7W2&>VPRR*RRo9=4u-MjIeCkL?^s)m#x9Z*hCLNoZ zdq!Pf<=$-*=hm2x{X~qztsuYhGTDC7Z>E18Cc($!u}Xav_@{k)=s%2!Z+pZz8M4kM zd7V{Nfie9S?`wBk-`8xq6Ctj5`qi$&z$OPxM~$N8QCl(#*>gV*c^8fp)rL1%6%=e$ z`q6hT;&5qQxRzygH*s%_+j(L$k&b8i8wPQ$9WOVZJ9)$+Qaw0gnzr40EB&%>e&K(E zmb=#D``lh%{#hgmH`LZojZsz>Twn8h^=h&`hyqE~F+oPm6 zxVG&ECc5iWi-bs;)9HOXez4Qas*A`m?{?t~k*j36$};w5^6Pb;VWsbLWj=IY6=G#V zBu~#VwCysd9^YM1AhaxtKXod`{7rvK>WYeOMtKsasu;XC?lHc+wmCUZx;Mu%FO`)w z|9EcUMI)w&&W?|}@H`*%jFy{dMNy9*aA}G$-p*n&?!EuUtnwWpmv?IW*Z$b7cgj%l zS){XSjM*Lzj8e)@94 zr)B0`O_~G47wN_aPT-EM5cu#!G88|3D5jUmF=*LXYwl&iO-HmwG9M37ZFb+;{`LE{ zRW==Z`K#hAvvi$`s__ED%r}KAnR8B@yS&S^S~PXYajlo^?HqvwMk_&EW6rO}`iEM! zhevVOn%|*m?OOIzby}{lS+{?xSLSfo!y_+sJ9TaZ+>hc45m3E+TPqkhZQk5N)h9hK zqh($v`e0V<=9$wM#x6vS8YWFV&@p36W?Xgh!!e;9YebV-*9D)L;1h4?wWpbT)vC4k zsJ+ATed0r@XNnm1(iwa!?e4?f)4iK=@p$xQy%`Zb>tlx{zA`k%8S{UX;zLT5bM>BD zXl~ui{h)55koGqHNEfbN_s+?d1AD{{xu0E@v`f_=mnUGcJR-Eo@55!qtmWlhg}S+| z<>T{{%C{;qfQ$ZUmPv^+n~1R*k8(A6)b-#z5m@K=Ifbn?nPG0mv?R|zT}X#>uiVQfbIT*_c2>sD$XX? zOA|5*c+I&tuQ>aRp=w#ii#Wx$lU(#{UGzhm(UW>zKLcC_&9=Em7in(G=D2An#({t0 zCc+gSP+F8_{4(W&^7P9AKhx!B>KNvl{7<~qU9JF@nC#VZMYftNM4ld(B(lZ=WFyjN*+dG2td>OhK|)H~d`czR_P zt>0kqvvrT>PDw|%P9Q~x0oZ^=TIi5+OLMCq+w8eiud>9gW z=V`>inPd7R-g|#GUl(2N!eVH4`1Gc}ty<}OpNl23T{LLtX8P`M@d?wOQ+)NR*aSvu zqJ+7l^6`y)K}{Kn_`wh6;n#hf${;@-J4fe{vev4eEkD?(fTJnf+{Zrrp&Wa`-qZ51d{g@_K3l~m>*Q>}H`|Z`*_b9T&XYJ7mIst;FS7+pPgZq>o)CszfwGS{jrk;Pp_QP&@ZvG zac_S$3pv8+V7aV^U*^13ebPVy7j@RSdc)Ll^Ji@i7p>zeLfTa0Kdjw*qS{`A{&v8< zZ^D%_o1cn}3VzYi*HQ4E4E0R!MH0qtps~ZMPE)HGq)?e@4}-wCG9!=2g@tZ4BTyd_eJ*0SUi8V z>eokc@>$DX-3vXl(kWZyguu!-EyKi8wM}!3uE8g%oJ!X2*7=m3OuNEUxSQET&gUHu zv3}hfESG~!!)F0TL9U_QXTvP)W_mzBmmJifdC zDWR(6akZvMo$^8VKALO(UYi_*+xip-a3OD=n7Kd6q}7tlr?xzQ(Gm+PRCP!a8Fssm@(<5^Qv&YML9r z$rsJeL^!j}DZg2DPrE{U-`IcpWkP%t^XZMtC$=2;x;FL)Lr9lW3^$X)h0~>YrQ(XW zkK6ZL%vJS^pWC}?3oEB{`Ml*S^G~wtoVOn~Stn9$9s9O=Sk?d{EaNHSd}8hbE!}S( zVb@hAe{|?Sv&BB`O!J$w>2rLg#|a+_f)Z6YBQ)Ph)`+rjGGZi3P6U7C%)jj1o!+W4 z&P{vjMea2#<6M&w`qzB6LhEm3xtA$l*Y?mj&^D!Lz*M&y_lU8Dy_*iFIJCjj`TKm` zc}uNUpS@g|!nnybw4EC2mCbjzavhqxThFTVpnj9v3MN-(Q9bMK-rIvu4|pllBu%V% zk@JL5eEX>StkHE7W37KwJGL~=tyBp|d#FmTZc`VK-l8$*vMST*;Lzc0v6so)-*HUe zy&)?RZ>?%>9djfQKi{0mQEU7FLa_8SOY-h@C(D=;V zk*Z$RJiWU7UEnw7Vxx_RBYMNkEYHNd1JusUCnBw5;D$$^3I`mtDS?Lifp@$6Zz5UNF<}h)17blRIgYB0M53 zvEv2x`e=VDCaZ8hm7fpeC)v%O+ee&J%P5{wjdk=}&D(PIKqAA(lGt~hIXk=~3`+^7 z3a4iZ6?4yWBz?cP>+-Y6@(066|Vm_&aj9n$D!U zb)I#Wr_5jH}3EGO5a!Qqvq0`-Ocx$RIt7Qc|H-G+M3x|m`VTjY|!29;~!FRiTuPI{d z)$2Wa+@j4I6@SjDlsJYz_EH$RwpJ|JwIa9j0qe?!xXz6`if%h5H7+;XVz5!TStL3x z>yWd*8Qn#-EWeLyVsAFw71>|w)spL(f1`i9Jk7sW?j_5Y;u2YHH(xWu)Z`0sVsgD& z51+RfWjYIXGNgFx*H%vEm8}u3{XV8=`*soqG@G;YhM9=bGq=~5 zUyyo~!*kh0s*m^Xi+abeXZUv=6#Y;aH2LJBy^H9l2J<_68LOQ%bAD#3-EOw#{G5NF z|7s*qgneDZhr}^ehJmhrdl2yNe&i3x1R?FVSi8)D91bAO9q--BL`o z%d52Z?iaOnF%QKL8h!C94q>?VZK{a%y`5oyugPJm-CcYMCS_-W3sjicFFKF9--=27 zCVkXDN>i#Q{E5nW(}^_A2;2DEYJ6<-t975&D`Pj_FN|SbyV0eYv6r9~edxdq3C*i^ zT@tCY(|ZG|3zm<&9xko$SC?W48+f3$sR(Nod~!gKOVnF-PxH4j;Tbk6`J}scAI+dKavW3Hzubu8DLX0K75vsb4ykJe&Zc3@5CmgmPWN=gYEV51Iy-5;rB zNn@Hg_GTu%heg9V<<{w!e9A5ruB)uAg9L-$w(qL24--%9t#f>d@i;zZ(IS$fCNHqZ zb)COMr}wy=`0c`M_v4I5gR#qtq>1#i-&`fd-d-(S;W zyqsh9(#7!u)Y);DSWX6S<&TzszHzEv1=Y4x ztyP?}`B_ANCiG}&?%}dpxfVsL)KGa#wf(oQdmKnCGHe>9DnH<&oFM6By`PS->*LsA zQ}^R$U*#tqX?bJcWx14p1@u=M?2F1)t+>rn({twyW}xI@3MP?Fn$3t+xOPjfrD(xK zhU-sezvZ>A2L?7oG2Ab@A=W~4ttCn2Qv?8DQ4h)I=%2D?<{H1jok ztJDp$Ja(%c@l(ya-LCYH0SrcZF5l zmFH|zw!A&Gs-rT0Kjr#cY&_o(f1EkyVHQF4NIlb}bN%+Is08){t7)dw^j4WYI&IE< zj(_lcq<#;sNuVjaD(NIag;Qwf0sCv+8i95Y;rf=Ws5*0f-+?#bSL0>Nql86`EAzBR z?nUbg-Mv*L-Z6V%tGJ1;W%s2tfn}jMN2Vc*j?)+2i_bNG-3V0Ex4JlQSg?MbjgN-h zhfgVot&HLVm`+Z`@i3&s`r3IOs8#CG3VfB3ec_ZLGe-h8*4cip{v5;XzPjCwf+E%( zLLZmCWPJ4cll{7RrhWs*`#!>1JG$&|-C=xbB|4=xNPoxs&Dd1o_iTe7LxtnH(^sX- zip`nQatsE4pDk}4$^TI7yzNQX$HUdyQ!8_XIzl@JV?6h^GJJgIvehhFjqCRDjQnhl zcbVySRqE_2856y*+RHvTpP&^>T$8x}beb~%g9CzPAg%EMumidwjHrzRF zw|YVQnrgQd%DHp4L1~f+20xz7-ahTeYINjTm*y*t0tSU$&-$EkH))H#8JJ5OoVQ)f zo95;I?!b05^Xl?D(nRSyPhH>m?B^1^9LlSLVdFV3HdbG!dhmE?h7XOy@bxAIb|Jj= zsZY;;qT|Oi<6e(n5wf^7?&&`=V87$cqZ~UyJ%LcyCK|_*4R0)TXn7J6+~PJcwO+fu zhPA2URA>5%iOo47>NlbfGCh;f7BF;iX%CcjW$9O_>I;uBQsJ{vy2Q(*&Va8T9$@z_cY9yfW2$uqMwF?S8 z>2i%IGFZmd^SSkC)yea`pKz}Pess|8$Po*^pS5a4t@V7Zq{4Dm>B^C5PyU)6>@-H- zo-LdGT=yU}F#e}g17?34iFu;dQuIpjOp&slOJNYj zPOu9)k^-d&ORy8R87Wb4$e#h7>_}LDCSo;#uz+p<)H?|O5^mv;kvJxS0D9TN6@u^! zf_oCu2%b=`5eOiLWRXBcPc-^KXV2djHQ_HHhQKoc0WnewM+GAAjJh*)$@v5Va>amS z_7_Zs-v84z0y2(_I17;c*T@h^H=G4mr@^j9GY82e5I#@j(~|(T#xyj=Pyz^lW|3qg z5YQwe5Eh(CAW$MQ^bFib0MwMQa4NKvgs0G^UkNaQQsTdfCbHI(u;em8Ai#_mrX-{r z544byUM)a8AQ~XI7Y<*FaFA_yYveFbxN&;IcO6KBUtkH|4>~qs;jZ&rSo%G&B9Qnv z8V`g_gNHQW5(?f=5{k?r4@-D|e;WC=0|flzEe13hNC-d+fYtlFX$E$3I`G{KopaqhXrsX5Fj`wETOudut*Xr5j=rn3#kEog{D6l z;TA$h_*3zOA{p-qK|K;%@PYI)^f-71p>MFH(9ngVS{x;4>esYJR)!LP(4v9f{lX>) zp_FM&LMLzwL95EsbBPZrzz~TC^aR-Sq7THLh2DmKKph5P0BkNwJJBe>CjExJld_D| zG|}&cI|AYY$OHiP&~K>9A@m?qFoGvqtOQEH22%c~ARZXw)?2oHp2ofTEdEc zi#&Tvw0#75gIYK>Ei)v4FRZv?=6hIp3$yC%npJMAPF)E;<$b=Rdheu~p9XKxO|~_* zdaEA1kH%$fwg^t^y=#;!BLB_pW&CyD3o_D1uTSjf-Ila{Maje7$yFvA4@;?E>{5?^ zRTf=%EBr?F@$EGWnBm+V<4)b_{Uv)%`LK%E?c%8tARoGoV#yVZU2^-eA<4tv6> zsaIrA0LyxObg#>`=!=GRZ(M-Q;jVzo>4wX!S}#2p2>%l@Jn4I)yQkcLO_V#5#v)GVC1&t;1XujS zsavtj4y|y&9K2D)S50##R;?+*^+SSqHez&Fg|V#?`Crd}?(axYRHfe#ryY=e z*R3x(@O+3&w@LPMKd#L~D-A;X<12LIADr(k#a(AtTYcc#Y&Ka4TxcXA|1t)(8{+1W zi1UPcFL!(HxnsL&$HUnR=G_d>d6um_HzE1eS8)A*iHAjz{ahD!=LD-;X1H_{GYmH6 zsV~B}HQRIaZeCb(O5x<3lTy>hf_<9%j^U+@>EC@xpMUu5PJl0KXRTB4zWERq6)M|% zmm;jkl=eQ6Nv)w3xi5(y4LYP>KWqCGc2Wi3c4Aq~&btqDgv7SA zPPN2;7SeJ5Q~Bq6x8BhQ634wZ(|PT=G4%4;p=BM+)SCLEmCTX7IS2hrr^Y%Kn{8^6t2?({^5g+UvoQDFG3^bS19MyY z^Aa*!%ZoL7B50Y-oCRzIFV(N^ce5Cm*TJa0lZy~o#oTw%E-P(W*1gX&r|I`%c@4II zD4nv|s+s>&-{`=zr4-JFbuxIUG-T6e!WX6T@WAegD{?)?5v9}J?w7n?tw?zi>#*lK z!To|)y3>f*UXMH6)wVOAf;_GkanV$7D46ThODmI^SF(tF9WQAoz&hJo_oJe-vTn>V zdilV)XlDEDGiy0I@Ve`FZ)|#hDV=tI_!XDW{5?mkitZBr)I3Oxy{h7bJXJDX49tUyBSvJP3JzLO7wnl z;w`VWwb{;1KNK88Hr!4Qt!`_7Ns`=jMRvnA8vKBwSKeQv zZC7mS+y7C#gH}0K==J`QeJRxU_+pDsHK}9qtBJ9}OpeSft0i6~U6WdWCb|22K*QZF z5pPu9Y^X`j@4j&@PhIkZX1@YWO7Jrk-G>b9vY!lioF7TEvvg(VkNHG*MxhMOQ}L`` zB)es=*4V4+o0V%YX+GF)t+zid1ZAE?4aJ#ew6Lar@9lhK_o{MGI87E?YN|?o@R*}R zhdLeG_Oi_R^De?(8;uly4vn+Uu6Avd7paWxe{8T>;z33nTL?B|2D>-cL7h6|?8K_W zdbLxxrpuLr=Tw?4rl^^CyI)*^DcR@tb=`sS4BdH0a<$er_1RvE2{Gw$yvEEp{D%8; zTJdH&(KxS%59C?*>R+#l=v}VmvZ-CSkl^0C?ELQM6<0UypEtaJd3&(*W-&p8{e?D_C2BG4gY5=&-mmsqbe$}o2XmtB{iFx#RWW&d5@@$6Hs)Pt|u0@GF3 z-=2xRopokMhUn3T9jO9y-t-ygbDWIJ_V#~WWpzS|Mb0_fhQ77(W&!p&%_2%1S@N_X zCEWVdSEuU|CugI7n|=5A#zsqt+v1PlJ9sa8VqM=bmuX$aZ7%wc+#58EX8RqFX6(TJ z7>xOxaH@lWL;gb;S5UtGhKE1p100i4qzBC5xN4r#=^%X9k?KNVEu~zPFQqE=_WDm8U_pTGqpKV)n>J z!)ERc;T3Iu+{di79tB*wg;l@xVkpq_hpk?1(krVYMqE+e=hDYqjvcr$FI8jYnliAW z`V`f9>Pwr)RZgD#`go0BnB~Xm;+mn8ZL9r_j@^qba*-ZCMEmH)N?h|XjCCfbhHW^sEYx{%O1LR(ghGd_Aqgir?Qq)4|@ev0Y0$ z&Zo>7@qOT6i?T51(|gqCY8Wy!UN?J8WtiJdf2%ux8eF=(d)TRs>gS`nT>l~7yh*>| z9n1( zd`NCu$RbjD!J1R}d$F6blwaTzf)wNoES#j!FaZjohoLSZ>juv+qP3{t-*+jJ^RXZr zsLHs7SfQuD3t-xUm;!kjaxu8wKrff}d9VkeEwDatw23I#p&BlnirQfUiNnFE0o#Fp zBiloA3j{0_sj(1J@&cf?3B?w4@scb?k3<_ZQcoa9fu1RZT!!BBeC;1s3 zlntHiBRp}Tp@T^roLpFBiwVgecpMIYtq35|graoO57?i`aoEKi1TI>V3b@x8TWVVX;_p0MWJv=?Z*HL5GnlM&Qf|#bEdW&I=rPG9efI@BD^;RyZ0S z3J+7ZAZWntfg)ZwA_}OmlAyG4L`op~?IdC?vK9AF%pZl=g^s#_FqA30(2B7nLrJMi zf?izbLpxCM=X)8T!1@&w7%ATi+{Pmvc`9O4Yr zpUA+YUR{X5l4@PVC6=^X>_XUo@Dwx11CCs1xWyY#WAUU(+~T*0Q44Pt02WWtN*0zQ zl?;6^gcF9yMZ|Q8jYNu+Co_eG7o=W51gX3bF-m{nmRLse|KcoJc!|~|Ora@Z*koa$ zi3vCs)(=lv3uI1(Bew~1b^&a|qSc31$kMh!1QZ&^DI5yBXwAc0P?kV{(G~Dy_2D-- zEROuzi6YBj3Qa`8l_J8FcEi)sWW9)36nGaJ=RzFe^(Np9asncbCOLxg43gs)6$c)> z7_PAtO=v+fsUDpc#FF|O(wq?~u*oK5|D#B-DE)B=_<^dzQYef<9Y|wxps@={j3axH zQhib}km8QRlPVEwvebHiO7D>8aj+090%9a|SU81T#iWDqh;h*T0-*v^tKd>0+-6%vRp*0w2DF)e&L5+bLAe#Fa436X+k{qbWF;J6#gydqeuq-f-LC_NX zgoqIk#keSBh;TTiFc5{rEJOxFIuv5=qv%fzES*S%Zac_A47hSY^^Spo1cyNyGPLsw zI0pkY$OUI%z#pqoGIW(g6P8;(`<44-ou%SVJNP*^ePP1c#^!2LuCw z77_r+?+^>eU`Qc`Lq^0vu!&hv3(^=m5Dhd2Gm3~L9}F=mC&bT06zwpm!9Z^iV~E-y z;-E2#McszOqfS_O2YVb+75YQlEHpogCMOmm=!=~NNp=LAN$Cx10sn`lJPvdP*pCPAraFas8Yx`RXwb|=)djA>z|0!Sc`T#|uoN~0gT_c2 z^%S0z3V;ZIgVnJAF%S_(r{Q`hBGnBZq!BgA9)}nxVhW;=ISMXtF#}T(dm#KkGKNTe zjD;g@jvK|3YXwboA~`pa_>+zR=?~-sUO|Mtfq@7l>~<^>Fb)iq7^6_xBDOKYn215j z8OjuNHx{)N17_jcMaqVVbOf;18-s=fht@e9Sq!9qD~41_SRAaG3?vsbih*zz;w2*9Gigy=eoUbAqh=L2E;a46Wpk;jk?0q#ct4uh6BDg{MoM6wA22BT11up|&b zS_30oi;%A94D<={LbygrGH~!_CwQ9)EFXh^76TGSdpjr|od0%O=U*+F`RiNZ#b^FS zxnE|q!AJgYUi>aS{$0rakXZkJn|%An1OJuKmA_7D|1$Ic9twWZePM#a?vCFs{qy*n z%l>W2Egegb5BvWjtm7|s|34Z3D$7e-h$3c=n zv3w?M@vGl&_{Z^Y043}{QC%HNeuVwm05Tj3|ILg=r~u(BjMV>&q=xl>nfY~!iqyJ* z62Fxjw&)8Ur2P7W;{Op({%-tD>SEjeclq*5yLYDg73IiMehEVu)ym&j9_jV(VC4_R z`pfwD9_{>H(UEjra(@2r1PK4;7cwZaF^nXU@|=ILMVnF+^6sT9sPorV&~RBg^IP&n zsc;yX)o7D+)Lv@ApHg*yAx<5C=y?A;{v_MEqa%Fj+%IW?oCG^e2UP=Ih^PjG1}O%e z{|tmnR?Pfi{!e2uR{U1_f^QZg1Eb%PBecmYm*!-?a0hIb@U3s{Y*(Fm7WUlLanG3I zC}thp*Cx215Aq&s;DsS+<<>6MTs1;ul%}d?oiX@XhTd@!sV{^lPO z&C9M{rCdnLH1jjE`r&MthKyF5=M6!RB;|c&d7oX>3rRZbp}$j`>cPE2o?J>lBAb+h!FLLM_=+`ZFcA0&Z^~AD|(!ZXVVMDp%-lTBHP3EeHFboz5RJ zq$1iQ6?pXpm)3C!VG|^Nq>a zf+Ick_kN>)qO!oA2xz$chnK!;3C5C!>d&N=`A_RU7u zkuCpYZ;q#C?E1AZuZq*EY5DxH5Wn`J(bT0+`Iei1DYw!|+4a-PaU0*56Tj$2! zzUXVIM_!(-i+bSVryc)_zrFvk)XNitcT`{Awqs!Y#=6@6-F?$Dr^A01>FxgHT1-mi z-cTN&H(S4$1oj<$%Knns;qUa$Phl; zjib5q7+LU#Y})tFdI>pSZ6EDF_M1Zo&(9vec`t&|Qh6AiLCIxJ+mnl*U8LHJp5OfF zRjd|qu9PnQ6gF*s!|2!i@$l56hjq>U!oD7V{66V8uxomA+sw~V9)GsGv4vCaUtX!> z<_qIw)47Ui_mikDKduWuG%*P6=xr z=H9lMFZ)L_E=u*AW(o$F%USRq^aKG7`iky%^4Oq1CVJ?D9f}7VJjDp00{#VX4f+Cq zK!c${fQAOVp=MzQrjam@9cYTBg2P1S4;@NW)bI~_ECcBvDr!1_vaGD|Jmd`694sUJ zimVa^bfEL>vLEMNohCp(ns&`%89OlaBZfkteMOsoJF zz9un=g1N8oT;*hqL23`#U; z2&n-m(F0(G8WA0G3Og+=f(JCFRVMP5m1zk z{J$w+P=H~_GEf7dG)!oCpl3Elkl1w4X7(lh1lgV;Oal&MW`pIjGh!H+k$wt)^rC@} zg7jiSlM;>}l`tDCHH0v95Yi!{=%JjaVxq-Tp~{n)G!qjPPbyG=)JR5A(F3O;aRMHv z0$mWaMyN9aofw(f=%LO?jamTs5<664)KpN*1?ieE3`HBt0OTzMQs9e)9)6->fIy0r z>kza-p7W&!rvjt~9-;^8v!Ppmp)LYUrbaWH9%@5i9)de4y8+x$Go!MYv2aJnQvH7X z1IPRyU0blwa%y^XA7wj2G6Wi|%=Gjuv@lHnhDuQmM9lxVNy26_{WTFGz`{@=#f@cT z`-2?c#agjb(=pP(b}`bRXhXC3NG9y!^BDg~Mwm$(F^e4zA4yHi%0SD1LeGMsW2Ph# z?2D8GL1AT~W1(STWTc|`1OKCErvhn6Nl)2Ou7!37Ms~QW=zaw`7R)cfVWeWESwIXZ z>6qxTP`VMC(`*A$w8G=J-ZEQoV}xs*(Y8a))o0Elj|(lRp9 zvob@+ve5ke9OQ0VO06U6XEFer^<}ay5&khv%Q>R?2C>0`z z3};MqtaLOeX=23CvQW{oljsfuDe{oY7#3FAe*{1Z{QFmRu!v`WP%+Ugbf14sT~P;8 zV_B)#X;`qdzvB&v5%9qN6&o3-u^=1R=;;}L(@9v_X~A|3JBnm946F=Df+2Jn9RK$O zV}KBYhLx2L3uyHMK0-@jQ+|t&^kmbim=@LkVlqUDzHGFt)W3=N-&qkYMS7wGX|#(f zj+Gh1%!;NtHP!Eb`#AQm7031)V#W9ujK%zomZRJSLqoB70Y2!dSmAR}%wb_+Vf#CL zkgp#YS`5SD9HyZGIm|{yi27&WhX3b(y6e*g3R zs?)u$>-t=u&-?xQJUGNipjA*j&msL2vs?j}OKDgHvJ3>nBY?x?7|+3QQ1B{(qzPEO z;*SiQ%`<)lBQw@CDT)E;2`+=V8+Cx@7=!*6;t=((lZ92#>YtN^q>3Y3*jE^qA!UD( zQe*E^Cr);M(!JVGvrvK2FW=k*|BeJ z9jbD#5tMQVJ=gy_ohl2adseQzEWX!CSufP?DHMlMvu1Ytyn#=^Uh-%20tph;sINJtVi#l_qT`E8d#~X~nn$d!7W1Iy))nao?!Y3-5KfJ47~QxCtLu zu~z*qo3HX)E0ikVw8Yn4t6$vt*6`Wrfve7chFV;2Wl0U-N7pqSEvb|B`J#(nXbymd-TlCMaw^2t~RAziD$JlhWGJ5W)rk);S!o$d|+Kw zpJr>0SFb$dSTCl{wBQMvL#Z!pPrn~@G3)DA+qlnq+qrFHw*)Vq-T%{&=pFs{dk-9c zRK3-PxkttarFhS$7H=H+z1oG&^>?l1YQ^uqyd;2}->;>%u4Rd|WA{GintIyCliNdf zg*kba_D}0=KiGd`t1qmr)w~_MlVnvNRN*R5-7&sVKlul(jy?v?KJ z$8U!|=CoZFANFZc$3?NWef!Gqn$@{~;l8KCz)fFXm0tE>W{HO113crK9vFA;V&cs+ zYt1^@H8$Jy;ERXt`S&}9cB=QLi^Xc))q_vZMtL?pnCyJ>^42j+qs&sDcPUkAZP>_b zA(xshsP-aDf6MG}x3MQC)#+QU&6(^_&vyP_=-Vs2M&1nk;bU&o)pu!b*#+Z&E;jmf z^KgTYhi~a7ChfTUq2#H`@gF_~41GQ$^s9E#v7PagukEaIm5|}z4?5O5+#~2mRnz9p zo7vwu`EBqnZ55jVX}Xx;<<2jj2bOR#-xX5zRCJ4&rAyx)ASbUkb**5o=8jxFz18gG z_Qov&PS+|gs~N(@%XZROO#w6G@{qB*9-4nZXWw0<)L}@waUlN*S#Eh(>>*AwV>z< z_qa*Jz6`lsTRFz!=EFfr6VF$#xTfp7Wnon|);+i&o6TCbYe1WaKdv5sNXYKd|5C~4 zFITN>@~J#&-e_BQyDm7Ye*m`b#Ep+<{nmsjH#R@B zIyZAfCDW$+KAze1cymf@a($mr zQ8hEmF1Xh?!ne)7dKKn>zx=e)x&8WQv&RRmYIv$iE>p^4{oDCoyJxS^)^c9h^>RzB zciGhC$3|w2sraSFyk<4&JL!izJ?jz`*n0TvclD>l#5SF-TK=W{Cf?hl)fLsa57(aoxS( zQ%ApcwX*5z*1eoQJ9qDKqqJ$n#QUKsb7sW%J?Z}N_OXvPezQJLI=*CIz|5q%(Yv2i(!B4vHul~4 zb4_NkeN`5J;oWOasCi)W6Vk_Giq-rYyDMAU?=N+v)7ZxYk2W&>Tw!(f3!Q@_M}9x% z8~<3Fr3zmZ8^7m8xf7Z&^4N~B@Ur8Y-pz^M_4eD$T}|p%8B_J-ard0Wdft^bFLO;Q z-63xLgf-M^<(#)6S@EyeED6k9I%dO{ING*rNBQX!?#~?Nv1#Lf?vf26CV{>9&y9Z^SJRcGj(k5&{3vT>b8~uyt-oZ`$&aTdSC(f#)heyR6;O0;Z$ z&~B6E3X@KjKSEwJo|(iA#R;oYZF@fJIP?D357=zGs}B30^)wyrlGXpH*Tw1On~-eK zqKJ-hJ)X~T+wItBeam|FvJ}-eCw?EY>CSf(Z8ggQiyNPOOeQ{9@o4M3S!*-jjal~L z-J71W3$C}mPITPZ(=~mGeg9UeT)NeN{W*sdk2DP%Usm~TREZMPHVtDEn{3(H0vWt$2HC7j$CtI|&UDk5$r4DL`qYgn; z@9r+)R`yGT^_CCi0(Q;o{kD3|)KRvl-XxW>oK?1O!!qR}Tx({|=;4)9z0Hw-&RyO3 z?IQvjw0rvUxWAI~-&M1kpg$AY+XWj^&40l5fNKxkJcM4STxR!b5@~0DXYacwHC!q- zy3*-<5AVf+nyFoL8tfZBW=OVoyPdncwljIsBH=*Ck7<4fyS^|4px9KHERjPHZ?N;y6b_*cMpaP{C?QFT)k`rtGDoxdJD9#gVygxgN%C4+=<1CNYKv^mwZ(}9iGcP2c(Z(V}* zxzyJMJ7hQ9KA@Y8C!J`!kRRSU@X@-Gd*fR?4YtkfHlV_hrUa9b{-EQ3#1jeXJ6?zH1y$)nhkbrIym%(L{b-~9Z@hYxnG|uio6z87!ogB}&Y(-LgI;Mqe7IUI z*RyG&@6q1+{mU~)p03%ld293KwSD>cj@DKCW^GN|p1vXOqjpZODC|u;k5*^MDgDmR zYt=!+?_s8pKc;qn?>{K@#wP0!_dQc*I=||7H0n~$r;K^GYDPDHw`o>m{OrOPmrK<+ zyg%s3@F(qNcAeEAy?oEkk4<-ft7y_E!g_zxjq&sL?oJ&Xl4$;8YmF&;I%m5?FX-NX z|I#zxwI=gP+a;Sk-3NFvRjoJFOFBZfdC>lF$4BqCS2<&Ux7q7v@2$!q58Jhw+Pb>0 zX>Q!B5L4G(FK#)nnq*ZyX5hw><*)`B)H$8|xscdqP2)GWHJLQ;m3d&SPuAwGr*PBQ zxY+UfRUO&VSITFXU6Api%FqUHYTa>T?sV*au-8ECY5&c^&3Ywg=iaHY$@F8>a&>D5 zdPbk`zUoAOx3CjkmQ1K`J0*wS+vH4t^Ru0+ULJpN#Fn?yYe!vF&q=#;-0f_o$n^An z>D5|qezvjI6P0I#PkeU$$w{47TnboS$9;dzjh9^;RqQ@$aZH)5TVty?)PGL7_v3p_ zdHKll_9t^HJ7#}ded+ESXK&Y47M&bYw)d&obI7)T(cQn8RJp#tb-R;p@nsebeb}*K z<%(B(C$@>`ZfnxuQDoHJuxDo)F5EDj-_c=TO3NYj=BjNLrOs;9_kHx}?*5KlJ<4W` zxM$O+&R{Ejg(@%QmKn!g!o8dx4}0O-V;ELrcI7Fa^HvQ%s(iX<-0D^VCD+%P-o7&b zekp%{cF}_!!iP4#kIJ;XSL$%iqOf(uh&`r(Zx-LFbhO3Cm>)XN``tr&__U91Rnuoe z6ZxvQ?=FS5cABQFWo4Z-Gt_?Cw!S;2Ja%aD)5nyxTU*3E#d8^(%&0R8Lg{&@cKY%fRERN5`E(auJPKtJNSf;`I#s7 zqdarlMR^Ck@(*9!_27&FWg;9yJXTMMANG7%`Mqz?c8^^8^eS#T-*k61i(aR%cbxKn z`SS3G^Cx;fhz_Xh8}p-0yDk1x4*1X1cbiit&@*#j*IE;{SG*{^xOuI$TiCJ|GX}HK z6D->d$Q;A0Ds#drY=TXVO%c|U_uskOuf&io%cl41iXDnJdmxN08xrAld&c2=u@SKy z0yO|wYS|~5{~oFt*-R?{nc!fUBhcOY<}nXGJRIB>4P^7oZRd4_!Fk- zp4uG;J@)phXxHHE7IXRHoHj?!RWO-m-g?<`R%KmFkygFrfG2pj6PeS?tm$-ru3M!l zT`N!MZk{$uSGIOj_etKvyk)B%oGPEqX;)kwnXso$g<-xfP3FJ4m?osmJ^r-eq_c~6 zAGq%Ic+HEe3m0@4I630guCR;>^XK**UTJ3dn(wN9Z)eqh6%%aUy>`{$Q)YL9n)x(( zJV2YcCalx_RNH&!+b-NPriqh(uX<)+li^EmoYb6%8>=@p{bqlCUu$}d=5FO=H^2AI z>L+)Yy)Jx1NA}Q{3;y-X*}ElIc+=5){_(i5hr5rD)~|POHg%M(jam4~u3v0gZ>sYt z&hPZfh3u(kr*Nl|4(2_-?7J7;?Yl{w$rJPSQ%~$H{W-C;dve*A4dx-Wu63`Eqjo1^xYQH_?u4x0@KT)~!@{TuA9^>-z_m zI=NwOPA9iVpQBo~tkJDPzjqeB*QnStq}ncFc+(w=YlkY-^36f=V+ z`CK^^R`-(kE8Wg#Sd>5yTid#_TWzmrN23DtpR4H3j&h&6w7L&7e8`ZVQ#bja_q$rt zu1Tx2M6FLT6>KZ}?yoy5rvK{`D_cHR;D@Iz>U%tSo}Z~%=C#IulRj3rxOC@W@`B7a z&$~n%8nj|0JLUDlv@sVteX3af#sHaB#D23g*Jt&$WH$_aTCwdL?{&I6&!g`=;UgBs zPhRB^?0o(CHW!}^qZ;BwTuQOr5B7S}p; z8P)*PEj$>c(|p?W((TSIpT<+t52Ot2cC@+vXzA@uA2e?Alw2^q!-SIe%}2WFZx1;T zzR^uTWzKF~Yj?KN_G>qXWfDK8PrrX^)SF}Tn{TN%`6OXq_O2#&QNL<~=Y?!47d-x} z>8M+e8t#36J*QO9x~r2y&Nn^Scy{}*4{w;a)+b&{)6T8mVLR>DFT^~VlLa@*oKT_q zwW|?NbFX(#@3CZ(jn2OAy)jpF-4>{~?fr0kAl9hLBB%a+DhzmE@$Ya{jU+uSN2 zsqloy$32Uh*SKl+Qtiq%BL^)%%-TgYsDG~Wr$c3b0~(tRCm%mpv7o}66{pw6`&o63 zc@R+I;jUo5|J1jGU%vht-$zYWwR5#S^7L5zL+y)RR)eR!{Bh^NmR8d%gvp-t+iva@ z6H1Lv&vaRkzBH}>gouzx^OgR!_K-GZ+4$(~vs`k$X8Q>pT#n-(O~U+J-1f59nbg^~ z|Mi-G$86%D1(r$UYuSXw4QzY;=*qfjS>cY&6a7~9Zg%~K=fJIAhnyB3sy<^t_>`LK zHylt{bwis%&M~z}dHv|Jygqw zeeFkMK6Y#Wc@Go4Cvv$Z!`TU8%*+WJW)?a0UbUfAvGweRSWlONOXubvpD(I-dNYZlmc+oL7wu5~^y zy043EIlb4EBPN5)?^?e~S~KhPl>0GGRle%1$E!|mjDOuC^hVrEOXmge_Plhlb=b6j zcTwqwr9C?i`80m?q~*7{Sce8Lo<1M6gLo5of8FshyKU^Ia#WjV4qCS^6MDQolpgl6 z&DkyFwfTeQsj)?0%g*_hcJ0jhCtvR#kE!oFW>dUvR1h=j%E_+rpQqb&aa*+;zn(LW zJ9e=~jTzkzl^nY#EoSnJP9+-L32AVDYLwUK>JJCsh<*~%%k_L4tW~{kHV+6)g)0@* zAC?*>#~7K6=O_%573Z$SD2!aq(L9G2h_srJt1zCXzzzyfi$cL-NV8a+kSWy|@mHKk zDG=A;9HCHi9Hk_Rrv_y~k5E%Gxsn$}Nv*==6iv(21aCyue-cKOiY6$ECyeTUDxm>Q z5;%?q<7h=8zE&t1T&+@YTp@ME3ZMbb6DnFpGBigL++TqtPqRv1Ay=swQmw*N7)jzv zC8oydUpFg6;f1MWj2x%rl!_u*jwWO(HJ!gD6hVE3W`zToLnJ+q?(2y4T8Y13Y7v^ zz$26#Pq3syi4i=<{3`OSn!uRff+UGch)=PFP5n=>m!KI1qhcAB#~In*1_Pe^xrzAi zr@EBs3P#DQlr+t7G>sQE8C<3y)pAxQ=Vc@;SydEu2^qSB{3~9-RSfAEmJUx;@S`;2Fv0VN*Rr-Se2aO zF^nf5`Q=I#O=A>?aSF9kNiqbcR>4;sCzJ$i14hyeO+gtD1dZb?Cs(6CYb1vnkdi+y z!IT&U>8j#cnTn?6iAX2O#xG?)k=z$ z!(`=3s`ywd8A=TsAyZJeQchzk1&OIh^`C#|7>pZN=*=dN`-<@(okDU6+_WV zh9z)lxl?LaD|Ha>+RzqgF(MpH6v#kUZtQ^ zYFtecEROLwUWC&rkX{w7WC@BPl_dKsPG`hwWH4!y6_Ad9o#>2`D_VBHfM%Luza0-rOFeQnpD_ zEm|*>Btg-PoTn8OM?$f%ERW$N7?(n=W|S&|mB|#036KqLNcA$*IuR@k$10Lrx?4Wxzq9R-xJ{ zHASeY0*z6rAm|E8%u1{{ryw{KRty-5 zB5;aSvI;o|JE&j@P%@A=k|H>mF9R$7scLc9T@^)Oa#cYZ;(53UD?+|%8R`I5K&ih$ zSQriDLJk{Iw3Pt9fJxwL&}5}j!7@tNPEt){ET|MlD3pIfk*rK1D-b-G1)-MFz=tvr zI-Hgh7^dLme=xqBRlurPgpV=|2S#GlET|aRJjM`8j)mPUFakw`j{gNzsZ=tSWho3- zkaE~ATnT9cq(tE)uVx4yCt$~Ejw;^DKu&3h+)reuCQywKw2J3RQi-#)k^@49nu275 zEmA1{K&t>$NCiexvix{*zarIQ%8LQpqgWLjl&feSf1X1rNm|Y*WGoG>1CC)72zHTe zqA(7VK^5~Dt-_QlB}6>0rdVJpmP2LD{}D`q&-|8J!7~&%%5r1ID^+-b*(*5?nhNB8 zKy_-Kq@my`gZ6O*$@20)Cqx49oK;grd|$;FoI{DiPe>u87zhC)PBX79!6+z9Fie%4 zQ$XR%f5q)Va8ZET@?44{VaqX)!~)eRqw}Z@&C@td0AQIMj*6WG(3?VyS_WvQ6cTBW zDrt8JjFlHxL<}yIiijl`8Ejb*9A?0+RV4lshRJEzTnQXOl^C9Zhm!_X1LTAI7=mSC zcc_AV0%9sYS}9W)nZKB`VC%S&A`~o82nOAVe~f1#FMvOgM~ci3*)3NpA*EC}MpMv| zqhx~U(xi-r2%tQ~WQabvpV>`K7*v&&8Bw6X#W-51h$~7Grzi#~ij1HNC4Vs`&nU(C zDQT4)L;5FE%P@{D!oMVr;s*^Y%`=egdA0(;sFW+fKq!zFM(l4m8OSIvV+r-2l@7*m ze`=aX!*U9W7h^$DDg_H_ipyXIc?Jckhp-4TNa}TP8OGoSMn;ItsZdUWYVn+s=TPv_ zw2DCh0{xL@DfKC^YY-=lQqsos$X_rmb{RAX0qg;*$Oj@&R}~I9O@L+p`lPtye^sHN zM8*b(RU>RD0M}59z_T2rKTpEFK#fLee^k(h{sR^RwL!9+Q49)^ixTDzxy)e(xF#fe zg#4Bjm&;&ia4*!B4BT8LlLmqWMmcN8@{$c=G`Is;>zztS-|2iN7j+w)Scx%^C^P_s zn#v10(rthNssLoFSa~6>M1K|?l#U=tuy2mwDS&ql{gUA-QYj~k!8Dc!o5AIZeF`C5WGK`hcAYQ+KOyC{lRZ?DKDoPGhq7*9lzZz1E|I-J{!2XP+ zMq?xcO^KW)L4fdrc`b*fkcN^{A_aklMk+uBhS3UKiF52<>=#b)`I<^0+fo$9VhSyg z20$@BM=etl#vQ{+8K5X=k__)MAQVI(k6`V$U>1r2sq&HpWKmw78J;nmK%eke}?@9M^P}v z)B`6Cu7-oa2{_KM1_qH)l$vF*f=XH326Hk76q8odaJ2$f3cW1CEF(y$RD_%aub~QN ziX1mkC3F~a9ON2s5n2)jPh*NaOwHpEoP$l|FgZAwNR`n1U=oIM;(u9Tfj09w1S^W6 zGNoLlDok(;L(>F@2#$m2f2vi`&KUC#=#(my1wZ8<&$A%!Wt^~5K07ZeNDBzK3Lg+sZ01puyR4OuQ1!dq*nq^`B ztO6_y=!1oFQejZpQ1Xy7Quza=bAX*nfL@YgRRl^R3gX6r=gAd3fAoXJ@BlQl1=j|u zrv^Y%5lH^zDh3dU&Z8uil1IS8{Z4%Fe9M+8fB?ks<9Q>1MRRgMUEpvvh!RDxj2v>4 zl@TPx)5Rb^+E5jud$1xSr=to4WFlpG?y67}1|pupRmBk-XG{+OZ5o*+3m}4MM5d5K z9YQGOm{VP#Dr1=Qh`cjSg3YrB$SkbC)ALM3@#ZYBtC~+Pn?30IWR~g z5C!8-f;B2B5(7sA%v92lYHG1Xfqlte0Lo((YoHS&(V^c+VP!aCa)yyHED$q`vR?@) z3QPqmi6&JnoycJcl$s<}BrFbmt^g$#5M+jjM)oHu!8l$8e?+8G;{=pyF);?5hvpM- zf+RUEUv~<{7$})Dh`fp{R6|f~ydn2Fg_6hQumqBj!Q5HYz_7fM1v#gSG!Z~#6e-0U z1cij$mKUA`up*Yv(u9mvQDA+fN(nLpRiJ{l0u&GW3KF_QHKkSp5%MHctlTp=FpV)n zN}vP<&Vs2Je;N_=*LQdv6(7VHl$iNFcd6wN3$gDz2qvvM`mC$C^g8Pp~Es~<`d z{pUg%YO?@=08gmD3>5>*{*AYzT7xbVx*6s-4hy)4=Ai;E!k|hBXhCIa9E4b<1np;` zKLe3eGl0pY8q@(ty{iKK_mscEc@S>~>Vkt=z&07Le}|PJMI$-X>XA^Sa+a163LLaU zOc;0^4t_*p1do$YZgMndhz^t|pY83|NPAfrxLeoP-ot>T2k%2lp!&~u3XT$f<^ndE*aI>THuN=-W zMyNPme>!}V<^W%cYkl&jQC? zqa(ya8ciHG9sLw4{eYfbh`92u%8Mm>HI*(-7f%*+Iq3=F8Rz^s<~33+l!T21-`;7aSj`j==Efyz@?)W9Be}_6s!@>X3(W27xBP+(t*|^eBxY9Y@ z(b+JNVKnhXx?v4s$Q?rqE*nDS=$yZn(7cC3emfeE<_F6$bS*^6nCkiSfM4m-dECeV!LV4giqHbMn2=uc)28TQmS~cNR9usw(fkRSPS$Gv z4L$r+nTQ@VnqO>?CP51UwJ2yag<`DNm#pRCb_ zX9Q{sm_r6^7m5vp0-mPH&}L{0Ds><_py4zbnta**wZm&G>I2cUesY~+Ob4#Be}L(s zL7je?LHeR)8?JGJhX!;FOIpulLv2s>ru zY1A(q=#QWh5J#VZwhC63mt0zn1xz^5LX!Ynrp>qtE3-f~1s6H#VG^1^t+t@MLL%rh zG#M67a5X#$F0(*M9H`YNXan_se=n)fjt6SQ?HBj|?_z>pA7}$NCZNz-Ky0;>1D!Z6 zJk3c9i3S^iP88fieSwxo1NRl;cKu{+xL&X4IK5UMZjlfkXra|ev;BVp8^0)G$P<*g zD9tqmVZ$a6Nr@&+gSOZr9Et`q8EwAyFYPK#Bkh1#rFxL6E#WAUHDYFJf1NBWQ1QYx zYYMDQQ+%Om0^!yo+XfGI{%f=Yy+G3lOhEI1{L(^kq5_1ZLf%$~Fy31t+oc9l?7<*+$9@_anPT zd9Mw$5gqHN2#Uj7K2X4{mEyA^bO+bHQoRg8nf9ahv(6w4nV-y4A z!hu`DVNeTXLQY5p#GFVA{H1YfjHXsx7ogW{0VzcL3l`)unb5w`}UG;Xp#2U{I9vai+jz;ef* zeq0=|;GxE6{hHQF*N$$n>kikQGk)Qoso$t;c3EfNH5!)Ff9GMTsE~FOJ)Z10tFUELpSw`DC)?46vfv(xRkS&b{ZC?s4P zRH1$k>-mZG?#v$@U{mt=dVP4ReINM@Zq4pHwucGdZBts$^7?u_c&krDbaypf<#~CB z-J?g_-kefoZoBCRHc~Q6BnRPslp9)hA zT}ycFdR}LJi<9F_UZ1{mzHav}rmi-rn!{V_&YHM{I&`gal|vntJgYurp=DIvr!l4V z!v?)xZ`-Wyl-s)3ZV!6#pZX2$7@OUb&vo!j-bgg7e|5K0EOX9zR>y8mChjG2nyz1- z9q@Iy(C+o|(jS*idZn9p@YTj!t`0{&wV$|U+>Y0yO`4Q)+Zkmw{z0uDNwXt{+FO3v za6F~|Bd<}5+T$E}JD-ixT0RWYc6#qnZHK<+ zpy(Kve}tGLnR=7(1!3JTTHLI;_j84wL$(9<%SfUw&cS#AC~?FDZgAe~r2s92o3lq3`OsJ{EC$&uSR%oAjW>Ju)2|-51rC=(xDz3YL;^e)z`V+ zf3rE(uEL%7w>sT0NuHb7Aq{`|V9)4)Ma$G_r}!s%?*WJKH)>%U9XDVMGZoE@j)CB>^G#LfTqo z2YNrOvwgT`)Pc#7^*6t8OH4hMFtTy~e~aPuFOkjds_!=$So!hds`C_8M;u;I|MF^w z63>P^OsZ_M%yQ(pi-F@KUfVdY^gPqV3bY=_SiJoP4&qgKSE_Lo1u^tuwvw?m3R`{+NqXyVCcI@WS zM`izZ+v!S`?h%f5FFp)y-+ETBzRPm%)z~!AU)OA>%?ED$k}rMRu^uxL)}3sNoNKd$*>{t-2=l%8@PTcILDDv~2U&Eh}+2t|*_}KOm{l+|x+Y>bNbm;-plt(G$rjK2! zwQXH#cCAGRGRti8b=`Q=e`JvBJ}$dvrge>KmIqEpjt{b_x~XNes;u(L$X&4?_*?In zhrexe(YpP|%J+M(#umn{k7`|}$+y0J+gp@L@jD)LXnWl&Np&uIo3(y_W7(Q_RpPWS zrZkFvy3yJE!mtIMcHgMGTru78b>;KPGuxYnwVCN__wMYAn52Z@fA()?`;P3~`=H4M z^Tetv2W+`ku8PI2{fBp+?GoQ)|A(1tt^K>W9=x=1Xo)@R%RQP_)xkBr(W837fp$@K zKPB6pY{>-m)_9Iz|K(Kdr{zBe+)3SEt5FyF-txm5P1Ly8E}ME?UE>#UEqjJ%aH9=5 z!`4mvZkMxMK5%^Pe^rBiT&?le`l({nW8OmHg`_j2aR@B0Y7&h-1besZFaQQ=%5 zX|}F$hl;+;1LjSOGxb)!dF;GC>hSo4amlONug6_OHP%b7f8EokG*0ok=hsbs$RuD- zrsbH(Nxlm&bSPnJ{cZHUqbVy7%~EPQ*q?iB>72EHx>u8jPS&44+{|wMeRoXX4$G7dNnGRhw~P8oD7Ui^m1SbGedECfE%Oa zw?Ms-2KWq(3C@WYh>Qdo1HxM90f5Rye*^$2g{LD;1Y$>C0mKeL5NQYU3+)+{AK)Rd zgE(E0;SpVcaS5VJfC`JbE($2BAh0BGBw!&p4eWr{BA~SZctUz2vPHTB>p~3xqAgL> z5DYe>v5E3BpMhy&*Dl#i12Z$&kpTw>QnjW-8P$tL_P??C$0Af0n7igkTf5Z}; zinxH9<3(Pm;Xtqou7jQp@q%oF?Ljd?%>hzvQH4-_L6XB5NN^#tqGw2A2Hq@&9Lx<4 z3!sY#Hc@PWq~Hv)Hc@g3r zwgG7=lsL2vK<7vbArFDhkNLd^!!5y(Qc&j`Gve|Ko!Q00C5tcaZz|O>jIl0a)hjh;EJIJ zW{`>vZWYf#G?8w><6tU~^Dq_2L@`N`vLK&?ZwP_}w9ClP{Qc1se*mdbmFq-WK+i+W z1jq!;B)~wBv*=~;0wmi4a$IwufLqQCKa`6AgNp7C=!gg#QG|ec2L!y(JBail zg$SvZ4)qSm48avaAYdFI4S2jD7Q@hv$aDp`Q$SsS07WIBn8M?sz~J#v!=QiY@d!)B zk{6)UK|Bw1E&z|Bx`Bj2bt9GyI0%536a-se&~Mlte@GaS|3zno20*M*RHrbr1hG9q z-3bcd{te)(f|#KYYozSZdgxFYYVV@D2VX&#BigvH4rvM^$pmO) z5OpAtf777@V638kph`x8gM0|*OA9Dz z1W+8bC&Lk-hytfWw}=3Sd;x(H@&!RGQ32TraubN1Ab_QyY}WxgiPR@<9i$#uH>v~# zL?W_*sY2@@nyMgfEhHzhA3;nGFn{DGfQ$$Me|3-q2p9y(KTsMa05Avw@*krk3ZQ9X zI}vFn=2HS_7w9p>6D}~g9TFK7DP*3IoI0d&2sDt}0cHS;3-C@{j0h6~N_| zf4gLaS?-Qe?IvAs8F%1H%Ar07zQ21tFt+;gTEioTOiaj5Tl3DEY<{Ck>*@QaW(U-~ zQr>k)PIOJTJO{>(ytp-e`9_avy*?YCHMyZfhXHLHVM~Abl=@cT(uBRHGbhf@?7OS+ zSko>nv6SeSMoUCw=_`yNUNp zr$@{hVmoAX&uUne53|W0s^-k*f88_p>Fz|QoNerslZ)4W;9wuI^jWlXeUn64;{`Kn z-gi27F3A3T=qDSiL1RvI2>LOk#NKIRhshFMc202G?-5qAR8MovBWY-8_O7SbKgU;z zifD9p)1G#pw$z<8lX~QDMYmj5=UrIW(I%ZfuUozUT}QvZEz`O^@NoC4e|~0LrMRgj z9a=oi(y9*J8(4OR-;_xUchRbPPxf27ELg#8Zs$6@Qi=E5Oly5xeSgsMl&YPZBb}+} znAK|2>qCwCX~O-vS0~Dl9J>dv=^ve9n;yL^H=wS6YKNx1hHqQ#x-|7elWK17y)NxM zFxS1q3wFX=qJ_R&j9;^wf4-mET?}`);?dUoaP0wgJ*@U+4yu+RN9V?OVSS~NEd`X{NC&JyagRE&qyqP zr_qVy%08E62g`1CaI&_j{CM|f<@EFQRDrcqU#8DoDqi@5$=iMF(wry7g~(4FS|pWerM~usaE$sZQJcMcyXxN z((Ur=nZ3?fG}x3;?#RJbPwc6y{WdCFbleelbbI&l8}FJ_IeOZ9aAV68v5~ReW=5>B z+f{#bt#Cdl*tNV@e`fnP9zod-ms&QqY?)PWYnO&u7hgZ>t9yRBLS*gSbx&OFZK;YQ zR>TL;TVL`>X9fnSPkK*J4KF*D88&c&rh0V8;fEFtyS2mN`_$J7rK?8ls=LK{#*R;Q zM$LURysPT=i01Yam-^8+){bLVzCGNvhDnVPC2g?5j|lx_f0tqJ-p>jphrk_t8o2~DX zGN<&yPhb4n!CA33s#H9ExkR&Rt)6z)5@TN;d-|RBZ0_!q+Un}vDpw|-+Sxw#=3(l@j+%J)>La5-KW$5PZ_c`S>Td24eAevf>X8B65){d*X#V^GX1!Xq>DF6=jRT3^ zSbK!R;>e(|GYdN}>{%z;`cUl3*n|=no2I_*;Vg_dpVYQYClae>F{NB_Ay__S&+pd$;WO*jSgI+iy^p(YNewJgL&fnj3TALCWFi8702{sC7y) z|58wQ`TZXy?nk{8YxYx%$o=v9tl6HSA7%}$o1#i@@R4a>tIVgkn|D7zfI<9$H@s%-malrmmD&?v3T^1NO%0v;B(7{che^N-QOR( z3%{Mw0pJa((W)A?wp1^b6?cC@?BY2@e}_jbv*=Rei<|m1UR&{a{f03o9)D{)a~aLr zr#h70vuRf|_RF61VM$l~PHr37?O}KC8Hw7C>MP63W^8LRsAa!#Z=K#Oe`_)( zp!dxyhY~DvuDm{T*Lq_8<-Q{?9o^7XKjFRqgTt{O`}h4gcYv?^l$sr0owMr^h23{1 zwjFeiyU_K)~2 z@Gi;MLOVB}`ZVrUkE1Ub7ptY&f06S3_%^%nQ<<}NL|k@%ySr1J%BCm31up8Sd40et zJGXE6;5oDQuUQZo8h^jc4wu@UKA7xjb@PCJME~A1S5|!I-5v4s!XC~W;-Xf2 zTi9nMe)HdjlkdvzU2>>D-lOv13!Y7PSXZ_ZLa(QvA8hqLFk|4<&TB92tsyUCcX^b%&Fq-3a}@mTw~ouv+!3#c zb2oG>OKP2qnwUwHZo0a1t8``_!UTa@Z@Eta$ zrrqPniE)&-XV=6ZC)ei=iWoNQ_6q+1?=lUhWoJ6Sj#)9W)6ThGf4fJr#a{~# zY3quAem1#{$I{emi>G8(JAQR+qZdcgH02&$Yw9?*)u#r1S}top1yReE2H#zFx%kO; zf7Nm7QuS(ETfN#_YDPfI{@>rJt4&kCWIiXZbMA&Zj8iq;TePTKuVS09EqP7JV`Zr; zC$99Ua=3N+kGWm^e|tneh%DhS`|27A80?bkh2Gv+(^Zk$rgY#?>%=FGx>W3ScXi5x zw1j(M(@)-iV`xK4(VH+I1>3(yYV&AD4oUo~)45*3qNutj{{T zWVepaH7tCqb;^o+wLb0bP_IP0r?s`^YOk>FqM5mLra;U*`y_r|)To;_R`1sjeX!-z zseRO)ek)QZt`2BcZrt|WvL#DYwX8y4`=(uN(|pUYe?H|dyqp{~ti;lu54+vpxZH7X zwVBU%CS4!VV$_Wx&m&8<%b1(K&h+KQI)f zB-Kvz;HlLowDq6aKM#1z^^!bz{kjQ{mS2xNA7bm{b}J+l@-!s&?v|92GvmVdrk?3@ z;5yv7f4jBfw3w$&?V0Ry^4@sA6KwXOOV`i!a6h zF>gsLESufwcB@6*1Jlc9Ps1h--x*i6e|a<2nl<`oRf-Wz}LV`|^ z1VRu*AQyBx!N`q*AU!z?9+YoV0-{er9U5b_*e$oE5W ze_TOobA%#$o1gCj=s`ZtB?v}6K&zATT@b4fRe;pk2%uUC2AV=w=|pvuw8KcJaM-b2YNJ`0sW!Q+U}5OWF!u|p{@ zfL#fqI}4)NA)gSyZFLgH7`<;;#1K)~NWw5FurfgqtqduqAQ(f2UcezzrNh#Uo?k3u8?9Ra07gxN0A-Gn7Bvk5 zB4=2m;U3Axb%+)q3<*XX7bNrnQH9t@mlXyX0m-XQyi5=zW;AfBAPy^@%#!L+vM@ma zNS7ifmPFo!#GiB`&t1ELEDAAgD>ufnJIe z69f?-kPApDDTwZh7AMu8PA4UeBm~elz`lrWg9z0EWTlkwQqLhmwE>L~CJN9X2oe-a zm?#MO8YCbMf?FjbMjI{|f7KX$F(w9zj!uUXSdbnsh>w?^E{KmuS)oID3O;YJc_9JW zJo0(n&ryOajd7N4%R+1=$_x~-Sd8c@sTid?&c6z#XFxukPUH@_D_>$jVWh`Ef{N7$ z`-{jx+(T*g=m|!aE69~--3CI?8M94?xKfaSTqh#EM2rGVA5M!Te__m1XQ2?NYp$w%W2*y&;<#i2)vJ#N;8QLB}ycT2x?iBeL-iV0+XALjJ$2v)> zjAxCfL{UfQB-u+af2Ot|Mzx?nLTW>9=H;xEi6XbbJ6)cyi!dMtNaPXm3OFxG4FWYo z4x_KI(eNhUXAo5jd88wt3=>4sfvXabo)wfc0jd;^7v+-t*JJ_lrB0Nyyn|@$JU;9q zhZXe{V=sF~i}Ts!RAo9ungtsNbdMkyBZKY$oXQ6j={7{#=oGRN@dbSTYu6Qk5OlER0wM%Bd%_S1 zoph-*4Rj7wFHNoTy-aA*EIaJj$hl6jjZ1qbbOQ4SFwon^c#Z<@v* zjYkU1qQC{lf3?DwGUyN(k|6vo5-T!9e(c1biriv2`d9FVF3P(?D5EP7|AO;3jEhd| z3dLH=slp4+kB)eG-gX;9Tj5X-wE%kUV(&g7pkv|Umd5();`7;c>|e{T}< zrc!Wz1?HxcW>;_#;!5GsSq0;69936%v_dN@a5(>DfBsyuqy-x1tV6T^8IT1bKkh=o z2kP?nAaBsTU=&);uji8Y-k3DfH7NAPKnMn7GmNFnf13C`A*<+47P&&YD}Rz=2oj2! zk0DD8Hz(vRP$%Ycfz-ojC8d<9)s%|*gGNNjGX(m(w=}1aV~iU8ld&=xK`3Mthci@h zi(yp^f3KF)JWaElic!n*CTDyRILoSdIV~-Qravk-sxiGUR0o`b#*BLP5$&Mu{^dujFZh;W;%;bLb=S{v{LB zDvV-joRrftg2yn9XNt2#mQpKe8B3|8zuLwVN>;@R|3K;Ta^Y_~97QQ0gshYWq>72lC8JUvfAWKx}Z*W7FP`H912^mkQ<&0dxvpA+CIE+&fDuz?zBwKu} zP^x2U3dcxZ!Th1(U_1qxNu$-UnBpf1L7xhR%TcRY$RubLUL3ghsAH|Jb*Tkn@!4Z|wktLWcgL^1s~RUzecrfA=`7R;s}4 zXf>&z;5)`D*JsNw}6P}mB@f(d@*3CL?2~Gs!5s$b%fCJn2G{5Ev)Kthzsl{ z538j9oKAjfA27&F8Hd9TfAcbol@3!1$Tb2q8zWV6CHj|PFdT#X3iu|-7R9lIno!~# zCuJ%I_Do`OHTookzl5fL1d;@jAtm^#95{_2fDvRQ!xQN9^0EytG=22F5blXj&Hni% zK2?0z+#p}#d+8~KOg1 z)$|N6v`s~C{pDi{$f%e~#-`&Bhs}pX>1TsVz;Ml*7eA;N$Lt~@{|Oe0+>}?<@LmE~ z>3Ct_Dl`UEK77qDYw<+B`W6YwFDCm3ul!*R8~fV_<)1_6hWmBmi< zZ{`Bhn4&jJVfZ^w`4=yvxBRPz6uV?3)S?9xI95cc|J9)R!;0Sv1@bRu_vVM_y-vp^gG4K8h6UpyE{{5)`@b$};|If*}|K3b~KhnRQ$N!9o{?C|& zLF)=m=6}Y-#MggR4*oxX{cnuW|He5Ne*N!XLBZEwtme-a+vAU~{0FaJKlp$AP8676 z(0_Nh|IjNae<;W+$oMLH=8s-pL4|JkPtPvsr!@x^KfNL||661H;T?Y`{J)&t|E1~W zZ^v(6`Ii^}grAT8epbI&-ak3@S1bEJ>|A@86xEflepXjM(NzQ=yC1Q;3}#|9I3}8q z#twsT0bO5Z1SR+>8r?0{A`?ZMB%AT`F?K1zSAvKnf7)!J7#{&2Ux)@vaYr`WL_NYnbN9ze5ySi?+L-MaVO^Z1=}ewRPG9Y2i5vn_Y2*{o)0 z{_qLy;DRsQ70pJN)sBdr_fNf;k*-FoS_#_)wc5D62(=31D6U~^X0&bqv|p3dY$UtY zME{cJf72S8+04|YTef?R;4HDl{6@Ig+5WRL7BpJXrrVp@)?4?;7qz>*p~zji>V!1L zdCR+K(qaR^q?6FiX*I425X~H%auR^&xRwyt93PMc5?*lQ*Et3WX?^VIm1lFUB zl8`FQb;%}j1#<=#MGy~iHkUWM1s;{n+-(JBL7~jUMQZnYj)E5`O4wZR)FYj%Ro6Gn z6)&*h@p~R{x8OO~C6`aE`c1g1;PUOO|@@5DI`GoVc!=%RCG)#GnLEo2$5Rg-{nUU8pKp;7;|_@S~7@ z;Lm2ZjX+f`0@+=|Tq<$AxnA5z0(u7VNDmX;GPjUz=X#)S948N=C7~a8R&|r%e?dh{ zM{LM$*-H@NEmElLspNVtaRt)Ie+-ukSRO6lSoZX-4LdI_ek&;{gfa!6Te-hn23$`T zPgs|H;3cOoZXB9oI`bnJR+a7Pgr<}@NJ^ed%O!Mbp->aXhQ*hOW6INK$rYo#5Rk~K zfz?VY;>7C;l|}Aw2Btz?R7I`Yf9%;^VxjQD;73S70EOJ;dKz(miisqVdD^pyZB+0o zk5HNy7?Dy1SA?@Fjn8#?aU(rMc;};c$^}nKL3o=sV=3v^jgPpzFcw%ve6H606Mg3x zPetDKcz{X;FB!ORgooXFFjPHVO$C_H+gq%34-~s|IeMF`@HR+#bU97 z+Q2)|zPqElQiO@L=RgHif2-v91@CBHM@Oy$R>GFW9I1!~cMs%_!cB7BTfqrAv=V+g zDwDvL99f>LY|Sw{$zafu=?WtQlW+)Zf!|7zy{89KYZ5&S;B8NL<<;C#Fty{=3hEAv z;kWnISBtnI25eGCw-0&X5sDl}qevP_)r*C7{rc)iXZ5aAs=4YOf5m`*_@eqL-o(!O zshz&h&YD>gg_|qTxhuuDnKIS8hSlpcyP9y=;ht`CiFu>iquvYk54D5USK#F;Ch8;E zNq;Y@UE50KM#G(TvpO4WQ+@I;8KDU@-f_3LaMGk!UQ-7M?3H(_`}1o4=@H5nv1$Z? z>O*Je@MRvM>_jyMe*qBdS|afxpV^!w`jn`heUWz-@zwRJ+!|ou6DR}~6JBx$RrMtg z3yfjCreU}2dhu*5y0O=|Xe#>>ZtuOJbo7;dpF?VqZ|7~>w&2IG@YA;XO$$D>tZiDh zhC_MV@_LD_X*k)iOxrdU_yf1LVVilJXb#$09D!dAo-CAVf0-uC1%dUdoAeg;wqY)8 zZh#2W&Vno)S!!$a(!@uQWm|b84+AEDv_ZI`&=lMT_A+dG4Ko$XASrOK!QQY@R>Qwc z4qOH)l(*1Mi@ku`yorifYe5N-0m6cl9Gd?sFs3Cb8?HU6_r!vrrGCMaOwY}nK^ zvL;T}Y{gbUe};lH!4YO2oR!a$Utn|)bki)0+-+E3uW7I#pe?u_9SL*5ft;v<;v!hE zk3t5aN60Qx-^OS`WN?;Ykb7*g$kOP}#J4cbv`Bxr)-vyyIMp!7qbBNx8ss&c!NRK1 zrWlyK#xz5#Y!*d>bh7$cP)(d;WbxL}vS#}Xb$1g~e-^#bOGktA7U2Z~X%NaRvP{!V zM519V1?x2m5_(7zVPQ@|*BS_YdWC3&u)=_i=M{jMLb1sks2;_eFky*^8P?K4gP!po zk!5@34JS}i0S*}kvNp>DmM)kHS=ijPYmLNkWhul8?`I}kLI|=9Om@VGHE8gVV%nfU zQk(*7f7%SK3Ig0@*c!Cio+xE0)(lkGvIpTQP`f26sM2?pXcTU_K~s<(tG%Wl+$EY0Z0 za*K8nRxMr+AOx&lKqkT@D&+>N3xgJSn?_{|&B%iZggz>4RD81*0~M7PlMkZh8rCUA zv$Nz0la)_ih*>>1OkNO}_vy7=$8cyWdM=AoFi=!x^0?yD!aHW9Wr;K=;#bIK$(JIHFaoeovWe;dFU_0CHS^phtZ&_RWlQy<_MxMmPo2XS_b^X-;8aQ~BUh@&CjrbbGB8HB#Zf6g~X zUNc!MBYwc5P7duCLAH6Xv`tn;IgzzF!()d9qGOh6Gb+s>!&)vox1d3Tc@Gn|P1siq zM%8WN_!JLD`Ak$1O|y|wVqwK8S%i^yJVNZ@E#({1t7^XG36BUv^pB-wEt4yYnjly- zOcMj+%Unh047*>To92;83+*1i^i2?3G<=vof)A4?kwln)Wd#qB zAY;&E2~2NLG9W@5G)!@hBYk-Jp!&o0V+0e!rDX-x0JEW}SfZrWAOjD8%yzSeKT@tR zmQ>r^C`WLq#!geP8L(H4`l@BIP!f;H^A$WiAg2Dn1Cumtyc9t*OFl)Wf5f;epmZtk}>o8ZE?QnMN$iWG6b7iWW;J12Zj8$OFXZsS>cmyG0p*$693YwlOH;07CP~ z<-Me2e9Gd-XgVbm|7YBh5;ji@Ey`OuA>ficp$x&J*_tS5^vk%nj{)Y1T+#SJPnlFzAzbD>p5RFeL@^Ymu9Sa#>n{Mh)Ttj^z#(C-N*mAyG~d z*cDc7Fj?3siq!&k_NF?`njr@|2P0LmJUeyT-Wi1IAttd&XvG_le~CL-K2oL%>Ovh| zaD71)hA2$BZtA+vARQG`9aVL7j4bDRp6$4|s;f9vP#4jfY7mZ+o3~wl01?#B)Vmt8 z0_tjv73jLkYlEff4m%#0{+Vi*$^By)DyKrQh@T;Jl{t>}HyGDdwdM?A>m1)IB_>SN z{bUTZ(}@(RyjAJ-e@X3dW}sH9FNH>{R$rYRsd#hD+KoLA@&HRfw7*oHA9tML^i(&2&?DToeQK`&!firY)UMCX;p=8S)#3(9@n!AqfP&e_q2RT+}jelFO)*n^)N#~`< zAy;|?j1_pSz&byUQr$3xi%& z?)7&Dzi#ADoq^lKAOs0wt#joe7W~`*da_u1KoJav5t#8HtjkgTO-$E$1~{h5a$HBu zGpy9ws|$5aR#Lk3A%6#5L~-!!b=XqCR31RiK>}c?Vbcy+0;*c#_yAgP+T&22cD$DB zYbO{ROi&R%m61Ny+jy%5omEj_(V<O*#cTpsc+9z*~KZKq@RY080hwr+@W3r5i~qzPscQh#;!e}s@0WGjH6Hs>5w z=TruaQW+cLB^|f6J>G^|L9Cf@$L4&|Fukg(6SP#Ef$Mc#*@a}nX+$!e8Vcg*T`Z4> z7-4gW>*+{NRNWcUDHVoTMuI=U3~J-dgf8$(moA!{iV*0k)_Jjh_ zA5ujp#EhuIg*qoh0TAdA%dQq381`zHu9B4w-Z#W1pu@rRrR??jPRHh=BX}U2nVorR zY7T2{pte3lWJqm+MBU+M20Sw8USG!~VpSCjC&bnCs(-4Z64pR@iUf#Y2V90#Fi%I1 zR+$D^X8CTa#7=ZUP-ZpuVJeAq98p)Vb}%%xd|lOf{o{oj^J91@^;%zH!&fOUCE^ZU z)Mq@>&KM@6;f_AuQ$*E8v>=Y4funK(*p$(kUjM+>0b(Y?YbsISg3 zf}5%}NZZbFEap%)5H+3=ir?oQO|XUsRVOi6H(7~#$|M# zi>uLY2WzEw{-FLTr~|VHz{oKU|5Wz>0GQG$iGR`O)>Z{{9@b1u=`I2c`IP4uSnPPz z))c*k3`BPfp}fGui2fKDA-Z*b)UXlXJW#lk^gJU8phj;lf5=};;@kl5gM%#h` zITH(HGLcv;7FQzSbaYs|hy(+Q9F-%xi+_K|!%7p{p~#t3R95gCA;Xz)GMbD^yP@iY zBB5wHszjoJhAl&m$Ah7$91P2uU?vaZ16HG|KOc2bEr|`R{GfE;5!0*eHqbUe-FrC=tw2p@Z!H^saB;qk8B&8Hc>S!hs z3CZ}anQ|bUi7KIRIx)($PKHuRDSw{f-6t$Vfe4I1Au|bCl7oprES3p|)4M{C0Y{-g zBpd-)rjG|;t{Gl~DY% z;OvFM^eS=8=Pc#~n z0x`(!R76qIkzgtvfMOAbQh$?3$CHtu5|1PyHQFM2JK(}LyTgCg^jjZX`0E;M?Pz`h zT)fiLbW%G=X|YG`g(bPsxX1Xo;cW{X-6f77-t!0?dxt3ZZAoY4zVW*zFRIPHDut8t zHa3I?+T;y3`!)o$bJY~aomelZL4#Kf1lE?~kz`jPUw09$8?~x!UVpq~k4zxhY)U&;UEHi2)Zg3x zr^m0{_s8)k?>fQymVDETkv}@?Xa4$>RO*d)|8C63w?>_T?|k9M^XL3BxA0%aPCfdK z+2wDaTwJwb-JX%pi?<*&rI_AP-o}Qh(^|;HQx@npcyR2i&tf`^m zPyVF8vG>ATwokw1(hZS~do4=mpWk@qpSM1|Y|n2$a`;)Z=TGlB;)2J%u;ZhT&bPn& zo!Q~a(ZBtt(g}06t$1PJ*t@p;@R+M#zV@1NPfkAf(!HNfXMge!yO-?!pXcwf_={t% z|K(Xr_S|~;l?ztw75Ty2x2s15tlvMLo^|@3#fg(w-InFYZ6%;n!9#JaoZ~bz^5Od+6Xx?*1gK#_lUEob|2mAF?T0Ipe{7 zwvP{Mf36(lu7B)IxPLh9!nZ%jzH{H2j~~*N$co1%Uo~ajv7gMk?xwH5blTI)=V_bX z``X3h9-4l|nisCRdi(`1ocGb|_gpAHKQDLoeygVS%_vu{qQBMqU_?iQ5)8CK$ zuJfD&oV!nYd&$b)pI`D=@srrpU%hgeeD5t|Z(edupL(w|^NLvuUYW9O-PdM(^|cu< ze>mrqF%v($>ysm*r@VB)e?PlU`D9;Z{IPErGuscJuwuWZGoF3oyvP1@#|<|>v*FH@ z?E5dDvVUaVWfQhKd%QXG!5LG>{=>LKXPxt<)6SWY*#52mdSR9E_zw>qH~F=T54z0y z${Brco%tW{y|wA#;4chdg!S)XnSO z-LP=Vn2Rre|0@UXf5pf72R6>z?|0AMw)mdo`u}igFthyX#KzMPJ9@$NM}FU{-h1}O zqn9qb?!@b79C+iJdGEb*!`!*|+@Gi{S@YK5riX(1Ge4j5(Ozet^@#l47Z2KU^`t-c z|9^Dm9t)S8e)^hIPFggu$DEs275BfR|Hzq(wiN#|?cA$YteyG(gwk=ZANtJNX}?)> z@Rh697-v2;`>*@G`NZ7pf$uN6SDy3E`mB7*U*;WmS^6jMo$=;`3D;IWShnoGOR|0`bUp$`+v>Y!KcGV-aY0^uXMb!ZQI*d&pmSJH2cQX zmCt;9{qrxKc$jp+_ZRlQaNU=T`}q3&bdaq zYR%cD^apb;eBW0=bq>H-G44M$ELf&8}?p% zt+nAiPvpKD!fk!J zKF71FFV6IgUR^qLPqxdWSN~S*ZGXZ{HRs9)rwlEw^qb^JeQWmF+eghQwOn4>bJWAO zk6T~5{yEds*v)4?KUtL&x+mw;(?=pMceYNY`Q+TcoDO(4wCB~;69;TN5H_w$X1CFq zr=H;p9oTbda?7CFpN}LB&RJpjoh>oNUN;@Ms#V)H?VGIX)sJ~R`t|#9p?@3mj2Sd? zQH4vBf4$KE>RT#X*z&PYGr9e;>vCAZ0-g64Ub1N8vISTAMP1h;Pj>ZZ=5!bqez8-X zmR)w=dEvXT&y4U3GgcXAr`!vf`{eqU;C*8@+HcC!PcNhDEm|Az*Z$zbm@>(;y9W9M z9_f;)eD-2t)x5QWCo8X=Ie(x*t+78BN%_=!rEiNVbuWxK?6JgcMz(Pu+x*!()5C{d z4(KQTD3I%7k9$#*+z&s0eA+mCwCCi)#>bMqFB~b?{POvs_nvy!nmE`-7k{3;%}*um zzil4A&#$A5M)H#@J%GOF4V zY0aq6DWkJo-IO)ab5dZX_VxB3`&#MOpNXD^8Eqt~tS?Mu;Je@v;GDfiZrOY&Um zKXRY#(Bk$%RSw$eRUeLnnJLC;ciSGD68 z>O+&c(!pl}?+=fg5`XMH!LlTJ*U&ck!%D7bThr1dzi(sTr9O?~n^uXYm(35{xKxc7 zGmTn)DaozU>;^5yU;bA4af?H0&0H_K)oZlw_5iP5-=?pySVd8i(=oUvvVxVCEiBJV znrN{wibb@rGRN2?o5gAstpbhT9SEMCk|~K(RgSS}6eC#~UVqeNRiP!87etGdw{eoJ z%2rtu;6dolSk?+-SyV~ltsG5(Tjk-M)uK`gG;3B>wb2qK@hn5x1RF!FJfo=;Ez1^3 zpm>H;pefZfMqw1V)gp zs>o8XW7cXBDSwTZIZjkn7#3Sz{H8itpec@pohgh=Sw)_qG+Y{`DY9gKT^Y=6Rjo2@ zqf}84a6L9f5fs>`Q!{DR!c!ctvZ9UWWr3j}5LQMO@mJd-OEV0o34(>O$Tr@hD6GxO zs+#=0{{DM*|8JR=<1IE7T4Y|(REuU&X#5f-tBp}bReuyI2$iHMn!-quq*ztOCPQu* z{E9Y8vd|Vr6gaCyX_Cc8s}h7o`)8Jg*eL=paTbk%2_#4fBo6RI_6KXAc}m2F-NwU| zBF(9|6j%o&is7)~*I1h-@HB7Hc#D;h7)s$R0J{IDOv}iu1}FeL2|WJRQnBLRI9il| zv}g+PZ-1o){QgJ)9Hp=n%SZx#Tbw`vfMk*4;eHmdsR=ewRX8XXK~M!v6GcX4EUc(d ztc6vq0)xMfktIfzCH$2($KnNE79~cpiug^G7G6+zk;V)_++>T$saD!zV|kY41jeRl zl&Z?CMU`z9iK8H1qE)tVicG065+yS~`W21UY=0Chu{J@~Y%Dxt1>j2xt3d%%fIdyr zco+ieT!5kwWJ+Ub+Nvljr-?SJ4F4$E2Fu|jQL<82#;Vvfo6OnptI=$%m9x@N$efk4 z$p8eErL6#C0X7NCq$JokE6|WSnN?`TW)(1CFd9Wm8qcr{^F!gdNQ;tb*bBu#y(y9e zd4FJlW1#E>2G&ipoJ``OQmhE0vMhy@(XfAo;vsGnr%)D616!^C##iH?HyB_ztEdV1 zy;!Wu$UuUUY_Y1G6=qO4NW4f*bw;D2*6 z1Cz5@OBR*2DuN`lfL0N~^FQrdB!*E~RRJcZD9{>8lX)m8jpk_3B%lsZ85Lecl@n;u zW&`@*fu$JM2GC(_ctTaJ5Ohryp~_i^tA*lJ8@$y-8MsXW%Co7W2D`9OR>dL!z9h*C z1tQQ+btP?#M8hsMn~i070dXKe0e{P?$_S#&^H#vT2DeB6Q&t2*#9-301T|^pV8vLy zoDCFC0Vz~8s9mTRNRnuy71Sk-vC$eUvKCNDMzUBz5Fo;mAObVWR^Uj~RamxWl{rYa zC__PToRyL!m`l?*`8!%y1)y+VrtyzbngH_wr_h?!LZzeQf5X*4hG;5iD1S0!AxWZS zR6`e6s~Qh0Vr{G}2((QAL1zIjuu`Bq1?Wr>Syn;{gSVUwh0+G3BMCq#=o~~quPRzO z7(@b`^1w${3#hS5zSxhYSsVU=f~O^*7@Hsf#R55rf&!ZsI0bBhP2_0>Xo{yfa09%m zg5v;U1M!wcs3cH!O#^b1S$|o9B>!u(p+Lq(2{t9lij3bR3PHp?0gphTL&1aV;6z!3 zT}oE*Z|ahQZNnWBOM{lfyg(y72Q&!NC>Bm0}o zEL4MqODrQp`4O^&s%3c#aF`XeRMj9ys!axYfV*MoERcW*g)hNaGLSEb1_3JU1jHG} z0X#r`i2^VJeutg{z?Q%lfhqzC<9F3bJQO~DGowYZ@>Uy&3FHIZoJJv(EwrF$GADo= z;zbsi*dj6(4p3vmX@Ai(+Tcb&I^Y{fUsj=bNMX>t3jbMXjkdxC3vi^w0ohqZ8xWvP z;bb69!J-O~2iXEZpul>;8Yqh5@yqtWu#rFC0~skGAt>^H<6HrDDt?!t#0a2%P|^?} zE0|x%DoCUV{3=V3Gnj~md|41ntOmDmlB8-1{>1_&1eMZk7JoE3G7Xi)OQKCwP~>a^ z0|Y>ElFI)#4GXMZ1m6XA2+(S=SztJox2YVgoTeuN7GNE;OaN)s!0TC54zkJP-+CBGx_^RSMQP)0&{6OLMY9^XFzg8c zOn^erOdGTTus!gs76mc{I&T9ChWMd1gHeGEBpcK>v;u%g3N%9koq-lfQfL+{@dwm^ zBg5p%K!XC!GZyS~pkOE!1P8ytoa0bQfpx*6QC3Moq19mFHr2+1NdhLq41c8}L+G=VHa{33KZz)B~f1VD*6)iN)-1H`w>7VwPG$MzqHwW;r^=hN9 zAAeu$d6%`O#VQx-By6wS9h~k*jDA`hsVaW|wLcrO8NVG^sz8D~(&VY%WP?v)1+eUVjxu(whXA z=?EbLZUY%N1tnEbvBw~{fuL4v8M((#`3Rnb$Znsqq!}m-C@awFW4Fw0p&Y#?w&*!A zAkUUdc?MP@@*VSq_@Kk*T$yF=jm@8jv^XfE2{)>dm`*<5s6y_%&1wSX37x&$;ybWi z)+tv~F;CFccWCX9zD@f1B7gm^FLEJoo5rueuXjUJbTZ%tn4+mUr-7MJL9dr_foL6% zp$|Heuyta}f};2Ow_nt?ZkM0)lSD{SLe*Sx2}AdF1k}O8zM|voF;;7nADV~CVgB=* zxV5PN_WrJo%X3Bmc`9KhYd9VMz|T2BC-$|$-Ug*VwIuGUChO+9)ql9?%5F{!q;%1< zd>oGr%f6zA?vzH?f(o_yjaSiab+Y>|C*A{>dc0+kBcImJeuixXuA?Tzi>~`2subGs zN~DcxTB%aq8mTtKXNWqb`r^DB6Q`Unw&BCEcVmRNC>SSaLLUKIT`y$3y=;>&pdgr% z)QqGQ+!R!51&uJcNq?G}!X=P3dzHG@29q_(fPOeNhP~#~{Iw66riZpESAClX`=~vQA2wIW_Q@tA+T|yub!5 zk)&rEJGE>%uV(4FJL?1TxIb=rzEEdB+l-iV8=RjNvz^F4ugUUvV`>GTuNoUw>;iQ- z`^#Tjq1`@QP=9YWXWOZXct$H$X^a^RpWWEufK5S{Levj^=m)h8@u3Z`YCpJj#zjl`jmuIhE^M_KnLEKnCA@^-3l=2%t-ySt!$6E)M!ajg`+e4%jGOu>H z4+V@}-${4>-2`iob%!rv;%CL|ObS~(|Le@D5ohvKfhNZ82GRB}J`HBC3mbs7ZtmmG z9w&Q%Lw_yY%!DvF)C%SXmkw{##eU9o+XG|Jq3*;Be6t)CKlwWn5+BvWTEf)H%+qYu zO$+*D%i)CXJxgBS+Wq>0Q8hmn@vRnJv~p0ly%%dnlsk9l^!XZH*WNuCIb{JZSe!8z zyOhk^fBMvK!=lNj(LPjI1+48?x?9sXx5uEHB!9M}&KffK%`yGK4 z>yX6fM>khQv$!TTq{w`E#jRsZGgEzi)l75y#r7!_dG{0zGP_~8T)&*|?ov1(Q)Zw1 z0ty$|<-$~7l3i$WFefJ(ty&#AHh$~cFVGbEs8bjMsN9EU+=C5qHdNu zocCzw3wn)q2)ei=p#-cnVD^=wy@DPN_!(l5bw`~_vHhTSVa5VSZXH`}CS|5=T8>LU zN!e_k2*)v{8_DJOOgx7aZQr);T#=S@ENb;=@NtPsv31o3AOS+7m$G}lx zT@P%F^3Yt(#J7duR2>dG=&?hF|9{}E0}8W(A?OA z;e9Z$iJX9jHNk;GZ?^~A9Z)7XhTYHMfIJ0P4z}Cvj>>jB z@SFpL5X%;3195T$%MMg`IB7?kknQqGM=;Ke*@psyko%A|fl7R`oRi=6CC&)j6#U;q{F@*mXggL9DLFa zRW17=WmJX$2M2?740ez*hxCIzfyg>eLI|+TP^29v9msH4av158vVRZm(+;YLdqm@F zhfQNT?C4n&f`Pwa?G72%44xIL$Bq~PxS+FxM^6#}!7~s7h@l;b1N6n57J&!5_p@g+ zp^9;_J4~h~@CA0oG*`AK$OIl38+MCl33_lmFhmX?pb|d^ zQi%_ql7nRkR4}#`@P7%4Eto73o&nnO3$E<&0dOQBj)P?qY+N*m4{=6kw(a>6&JNu928HkO8DRQ-Z*UYED)lctkk5DiTrxc3t4meJG%{Xw?;g z)IY9)2BHPu$b$VAQSnWq3%v#7a-1k>3Il-SAPWL&Cktj5vnY~U)6g&C8{m>y zU}Sw27Knhv615>5W%S@Sa39bQY7jsM+7}tUI_f><+vU}Xg_jj5BCCd8omdNA#I}T# zj8(y&1MP*4SOqJHIf4xuT@-T>muyyvfMTv-mCz_0Ab&A7JS&03Af-Si(cDs2G`9eI zRt7yJO@M~>9la#!ndeEmkZJ2oc7WCEEl#1!gu$lLgYm z(pF@SpaXmiW*w6bxeyg(QFN{{Q3i& zqGWcFlnQnuU|TrEzp8+$E2_ex!{gEIBlQTNrvm)o(S2IL@<=Nr()8))#1w)CV)iW0ESIL%@MBV-}E0`*9N*WrS(i_vT>D1Rr?uR;U1 z(bAZ1g9c-ayv2j10m9RWY;y`WGN^!9{za-~=8_B(IWFIcV9 z`h}wX@#cW)e_Yt%7gj!K zSc>o2PZXc?NkYTV^M8*Azkgoo=aG%peVF^SU7~R_zSsLEBkC~^uGO!xc<%A=oy{g} zqe~~2*jTD^;UOKK-%q$ytovZ5-NweZgD+Lz-sD=_*YEoGZRNjWLSXa2C4q&q#xLLL zU1DP5hA{(jEG<=H@~<`SCwDFyF5Ww9bng~gmm1yI!#Dr6nSXC*KFYbsUVC7dRlNqx z+)=n$%1iG@%ZFs1GVa>41)ZA@-PWr4m*{uyhiCuE3?BL5g;&pKtuq&#;s4wFkm3V3 zmnvOm;Dl3wtMcU7|5LNYRU6TxCS-4}j4PhC{t8DmpOp*mUH-G&dF^TO_^Ol_Vr(rExp=|=$32amKAH}GRegU?YtcLu}$x6Pp$PP9dW<#q5Ya+ zpYy*yy=Qi$NRuY}Vr{RoL===#|xDp1&XbY&Po|JSx}oOMQnP+tU2=gOZcZc5a`&rDfyX1DpNN zEg70ScYn#|AMP%u`|hncJ#p&fl!CsWrW6lpdaKyYpbaxGjcW7u+Tx8nH_T~Oxu?Ie z=wQFRnZJ#`bn^VF-3#AueEcxpsNQDYM0<~&Ava4F9=JSScr^O%`9d*n>~K-?`^ELd-WS!vD5Bub1L)2mp-o<=H*)>cjC}@Zx>I>xkLP|<%T06 z9`pRO_>SYey14~JW-9Iz@oG(_HFw5$pAdL$%=1A-TZJW_sh8)c-3e<7E&4RTDA$jf z7k|;N!-u31T`Tsx^IP^iALqXouRLDpT_XFN$BXy%3JGvzs_eO_`dWKzw~)HeIz1V7 zt60U{C-XiRm)z~Q=2-D+^2)D|8%$7hJY9S9H0>XKdwcEecO`dGuTXSwqTR^2)~TZ}*Qu842$(1&j-oSx?Qq{!IW9_JQx>KDGH z-imn_x4yQ%WwVuDfB(!;^>#y#bG2%feGqkNb<1r>ir6X?542vpwy@!g`Mt-q{++)R znK|}c4=N_xgc^a?T`ijR`o=%b)PK}_ep4}EdUQzM(69Y3XB~5J$z92B{F3hLvbsNh zILCYEmz;nb=reSg+G{Bn8^ z)vR@N?drQpD}x@TRcH$H02=9u+mW(@pu3cZ#`Jm|vUl|x zso9#Tp>q9Mj;2$VJ^8a(hkt52|LoJT$CyvM%hznU<>k(8VO3VwZ&IkSXA5C?|N7$# z#`r}vtsT93z_mrYjk~9sMYX+9XKTmX;>lvQR%O1p@3VKm)tM77t$plg@em@Xmnkv2 z?A6&V7IYiBb#b1#%c@ts^ztwp@HFe>&?~iH-L*1;ie!5@HeSgd9{lyN=|90c} zlv-8ud&X}(x;v}K$K(-r<36jKo?aR!Wxq45_LM1;7Ho*R8(*h*(!|#DbB^1TH`_1E zSO2-?_R~hvkPaVzZg4ZX@bP7H3oheIU6+dl_6htVEEJk7m{+25i=(xl)M`Jz_<(Oa zW4GSBU1rPW&#T>Q4uABYsQqF&S!UH;pCW~PZ* z`GWPVn47@(C;;1_nHZ}fybVvM)p22e>c6C}3o|S%7`AB8Iy!*J<*9PPX z+axWwR9#PBTUzVxM*7{dx)JB7{nsjvspZl7&bLBOgYS;{JfY$*i}Ozuv!C64ul%92 z`6@2j(XVQwt(nBd-ly87+-y)``?VL@7rZ?+Xwk}k+JEcX?-mtG>7ZR2y1iS27x`D5 zZrkHAO0LXh zZ(Z#E^Tu7(2R30^TrCsR^xUZV?}LgLecmxC=G|iN=My`ekE<0vNhuaxwBD^2_9FwI zT|ZqXv43U4>It(PqeeBIG~aj6gBSZhZ7%wYw2EaX1@8^G z8*}*N;B`x9#ZNrzSD@^QUMZIjy!z@LPYRg8TQ5%ihwv zTYq`L)LNzHpHCm3HYyFh+G|?BxQf>j%e#$Jt$o{6iB)@?{AJZbxvcQZyzNyuYlWpA zC%O*48+|@^;N-YJxr-w& z4QOs}pQ|rSIWtXcR3+0LpAh|aNbOKM_FSDq z@q66dM}Fo)URig&^&U}h$nPDGdDS^NK9uV@@(myM;e1?8-#Ni;+Iz)+sYkba7=KQ; zjW}{DThveGvPXL6OyP=#ei(b@)`VPqjZHnP7m3}nrry*7zYLDp)Vb648!ukjayC51 zj2U^ST(&#SEv0kZa;tMIYnDbo>6bq**ww5_+0m<(hZbA0Jz|z{ui>kzWxU2DKRE8u zH00ItjgLE4U(ZT|@^2M~)Y%_H4^%QQBhvQ65%%+vkm%HJJatjPK?T zK}jiV4isHk*e%ni(OI_L7(ZUUTD$x4fQldW^-(uAb+>0K@zcxAhx06WU4N-kk$W9| z6WiXn-#K6GrYakQZo8juQy@u;e>yDSz=l8jR(`v5&!2O;&azZtdxt#Sd8SN(0Jq@W ztFQY-mTO)*lXud|EAy*<_2N4Wy*P^TTAKKDbiLP6waPTy_BCw7ps5v`PLE06(fmzx z?T-f=JUO?gYxkAw#sn-bJAcE^z4`MY<#qq-FHan`4ByOgqwHbeYuSm(jj`r_@@`1`ckMct500XNT?U&GijFbuHSttVi&+U4IN}+;(;CQ*WQP zuDsv3bMG0?mc86Mx8n$1TV6L`LjQo~vtFHU{yNhL$D(mzxwD9M3V+&z-aW}?ROmcC z{7SdnQR8nPnY1gH&!+m;*rm7Dw{&lrIQVEI_e*CBHO`aC-oNJCheC^|u|kdF_LwS} z<)~1t?ZCIk{yU&$+=aaWdnlW*osVFl6Q7Vas8%iQtGwESN|yYdC$r@ zU-A`g{PVhr(&C1BqW5%Mz3|nqxhG!fm9KEC_5CEa?3;s?OMjee>i#5;@9WR)dta=P ze{P&lNK(sZZyq&Dob2Iq%`i8W6u=Mvv&E0Z;is~?QSgw>PYv`gOjl9F2eKubDKZ+c- z>R1;Kzu=s;-+y%RJh`gCixs&8*S~D~X!;?ayNwIf>`1# z(VB>P!F&7lzSLt|t)g8PIV$8^^KDcE?V)g~%72*!hYt?SwP@9_!@Y*ibX3VPuK4a| z9e>uVo+`YmX59^4zP?=0Gcxlllkw_UvV^oRGKQ(XY*$3VokmgC-}^cyemC zdw=}df;aQk>EW1`@7H=l?1Or3zZIXhs?Vlnrvj%gY-!8+X6=Z8q!YV*PQ<*7`#o#@ zNqv5;>m$<*kLGP~I;4cp=E~zszprl0nz5o=mcn#w$|Sb*_%jzS}$Cr+uy@7tebWUEqajq|~ihnL{pG^<&ypVXlMg#kr#7p%GEIrd`M8_$^ zGAY)RYbNjdWz#gzVlz*G#k{}5d&zvaemfufcwhAvCmxu+WK^DgAUChhweEg-X6AgY z&#b=Q?RnvuKYu&lV@10$V{4Z8`uI*C(IwK-eD$2h?cQ}7({j_6KdQ&5JB032T7NyM zv2<3yPsf`L-o3~^W5Sn$>$@)TwPiU{e{jgH9+BB|jPrcH>aCP%*52dE$UNobw`RL7L*y6Nk%2;i#eACO`2UU(AygRY||)J-|l zabjHuOuI}u-f5b0jI8TcT~E^l-D%dy1iH(u>%>IsuqDDBIvQi080XYt(0}!GI+kWZ z)9aW^^mXFt^>l=*>rU^e>&}OC!ayV-X;rPKLQHqvgF1t|N~beA%%JOF*ASI@IE>a2FVmCiruQOia4i82XSQ`HYV=-^ZJoG=R7mScAK*)LlRk8vK$?hMn%3c> zz>4bNp-868WSj0o*G(aT6Ms(kp(Ej<`_O$|n>5|WKY{{BBdnpL8_-G10a;12oN1sz z%L$)STN52o0_IECO^*nlaV|7iy6!YsI;2(y8qjqxSi0^qSY*MvsVTbd+&XHC?m7?* zsUOhGft5po(~;_RG{C94r>EIE9kp5~eleYUgb{EmT_@H`M?%+41%K6btW7LZ=S=C1 z5PnWAQXMg8>Yc9tt=-4lT>*oCBogMJ#187ZnNwFJbj&GUCElN~B77j;-&qO&;L-H- z2YL*XTlMsgHO*(hroXd7y6$u>IA}H zr_#AjES9drBGPP|u786^1C~I)shd0eq10U>L^k6xXTBgp=Jt?RU<t|R2M6awH4zG^H*jydNxf+YN`RgU0H`LD zdWp>@d)9Su^5(tYQ7uV^?(%p}UrMybyxH^@aJQ*7r1^l;rhnGx>8dJ~0Zq2nNj#90 zKmeSzsp~`_bf@=8$DRLRt96n?UB}6M(cPqWJTR71uW(q@D=>XJ7&u>NKyZ9CaLAiF zs-0=Ib#w1d62Z6MFVH-kUP*Uh78KB|O$ZrbelvV7Mb)vib)r;%<$5PHfF*R;YAXBu z;QNSBk*87t<9`$?oa-+nuA8?Y5>pi&KL4dcoYV^kVZ856q_gB%hvsSQ5G^K5!3lG!S2BNxGUF zeB3NaGXSZ4q7z%<3;^*+I%xt3c@5F^v?f5uv*bX21b=ft=7%S-U(tP?{HUY-be4jd zDm3MnQ`P4?oif?UOpJ?9OkYHXLVb2IEEXfYrX$GAayl7G?}VMrk4`!l5^b}V)9^@e zOn`-O7jok{a|j(QD#W4TL1LxB4w!C^cqXjg2J>D1%2XSdbBAlk5W@r%pNq?>YDD+g1jCWv9praFl_fD%|9>ml*52r`b^>oDVBnDDIt~IBk(*y=7oV4(u zm)B7h@fl~CLL|~t4)z483n6a8W|1I`AD4gcijddD`-eSV8}3)zuyM_3Xjm?v2tm!+nM$@%|*EC5~LEK zh^1hV(3l)W+^oOpN(~Y}lb#Gnejg)!*`M47nu>f>pW_K;n-iV+MrGZ8*Ki7JprEMg^G%axS=FKqMB+#vu4G%>danDOP`j zd@xd}oaD+NJ0Ni-E*6a{*f@a{lUYmP>oGD0)QbW38MP6l4l(18NH+WpvJS&^GDs>0 z39eajPLof}vVar-*#r_t*(C5c$R7I$f@#blckt67levoD9JUYQ-idvK&@IBwNPnG@ zguw78GJ&A=2O~#7D*)&WfRG`8^FlE_O9U1)2GE1HjcAO4Mi@lY-ye-I`e4&C5*O|= z)I?m(c1AIr#OY5KZXoxO(j~SNHVu0=oWh3Y#4rGEhSU8S20;_npCF+8Kyg5w8wRl? zE?P|L<4^3YK%i)V@WT9t0i2!6_J4xQQyG}qRIgwd{-&EYoQNV-Awa6C2xcbW4pbP^ ztT^@Esj%2XIN1?~GR4a<2(6fY$v|kCLjWP;77eH;)B2+mFaZ1hu9`LwNMIQS!wf|d zXfFmT1f4>$3B@|AQx>jPzlmx#}X9l4qBgw!s2C5+tl?i_h z95G+4xl1Fxtpxia!0yTNlT397@Vs zMCy=a^H@4Kx@wr4h1wL*{G_%oByxHIC!fQRsm_BG1ybQ)+L+mVZvxbr6 zlp9kF(>T{Cjrljw6bUG5pof_Jy9O{UY97is$p#9pX%z^^6ElcV1p!A8B>AIWAqEVz z3Z|8q$B4U;-7^XPBBUz@%V@@D{V?p~?-T4_XO-@EHOqYEpWi z;f9$I4D=bsU072(TqOV9VFJ*6kGM!C=u2hszkMe4a28B*dVgh}U-30LIaMA0^&o#c z@IPSvKX?)yE?9>D`JArfA1zPn9dP--A07{Mq2Mu{j^%&klC}RcZXtjA2e;t1|39*F zUS3|FnpVS0^Yltd@$!V9o}OdjWVC0x1VnpzX>MaR*q_%wXUX%gOO5Ljoo;_Pc*=UW zXwPU*FRyFMU? z<@Go8>Xib~bJJ?1ctU?@Q@r-O#la5${or0HUU+D6ZpFP);@sTaG`AXVUMVgldd7MF ztL4|wAeUy}ld>S%lu*wUw-h%ow*^|X=DFX?Ye94h4EpaS6dnO=cxf6yV8Q-q%`3$d zKCb@{)WdUun`ay;J(x1ZiINmAH!shW|E*>w<-ZxmEADFwSpa`oa4PBaiSu;R7C@HQ zuZQ49|NUfcZY}@UQ4kFaOVPakl?U_k#Hd4M?RRqaDN^t;@R|@#^xGVrGPtWLRw>WJ*;MUC%-x4mJG?r!@8AryEabz4B zN5+wHWE>es#*u$<{2w{)WgHnt#*uMk92v*|{s9V<=saSb{}NrNi9W?9ra$mJxrWc0 zFX^5(Ka$5>V>vIHlQ~C-bzLLpQg2H?nsdnSloB8QZYAa{t~cq{kvbZ=6q^_u``x7< zhaeZo+t}1~{di;gm^sfzyEbD^=DhI3BtOoX`n)-3+R1-^d;INy|NnZE|Ji|y`+pX4 za!cCND>IIaBjd<8GLDQR1M%0mC|mpkq5ND8QHlS#zOz)5*e3CU6V|!u9f2)uDvR@^v^g) zv8aE)4|9JpWMGy&XZy=l?k{K9l`FpwEt|GPG9RNlL$H!cw?K&1%G9Vp2x*!ukNs`} zCf$42n#m(Sh9m7Mg`y?PUl!?H8T;2Tnv3Gnrc=IKqbj9egYe_?{P!Q3bW8lFgT*{+ zj6zds=hD4hsj04=D(N8dmr=+b8QJ^^3**ymT4sMmIekWCg*9;^@EJ$Ok#S@k8ArzP zzjs_Dhxz6T{Fi#lJPXQ%Kdv~>;2q$JoHt!bdlpW^ovwf8z0T*%QSj46*D$H`q@V5L z51&CC%_Wi%)4lzhBlWrTH=qef8xO?WT=MmccQ4*r`41zRt2AdK4};MlKYv`Q>jZe& zi>`m|r^Up)ekJw3)T?H&uB2P5D-W&|xXxTmH=OhSi@11lLuwq;67$`GLtjrHmvo_b zo^#D{CH;qsSIBlQ{tz8A7+2EY_jkc_oxh&;@w@ez!qDyp z@Ay4JmV&lVWj?RBp8xXu`Yy-rU5IP%r`NWez9yY&Le+V0fArgoipe=={jvAX zs-N5w+HWYo#gf?8r*Y3Abx%&H6?0|tP>+~yuR{{{udP(-e$$F#&4z8>WPMk>RiS_T zy$rF$##8ZB!sy}e?QT`~e|VL1#5R7(NdIgLm$z#6sOoXD{&Zy&5#D-MYTn@pN``GV z(}FzkL(z1GkGb(G25+n1+T0eNi||5rNYr&(h4w z@Ni{Dj(Hj}+^h_jWsM;*y^*872M&LH1D;C`_i&bv%cR1;oD8g<|6@-)G3tvCkHPpcG zM~+A~Z@uRc)B5RfGVO4eKgVrq@L+w9SHnBT;5Kgrd2C9zSGf5GTK^mY=3svw&76bc z%Z=8q$fs`5h%g@&Jq8pdpPlD(05(W4ZjkKQ&9kj9n#>rKohgtTdVt z+zlj&D;hy&CJ6}&8t6(rL1~Z{Q`$sngn^F&0$L}hgO-OM*`Pr_#3Y-`A^W&t!~~fe zKN1uk(|Wj(15c+06b^gDE3BG)fW|l(d7y5H(i>r5GYp~(oR9HOtt+Yd8ssAxdCcQhm04klcZ+Z5@+)~^JYnRenS8_5bi~5yU z>;rNs#YB|O)+GapDXHo^pao})l4%`MHGp@NY9AXnKCE|?j&APv4?A4G@buiVR7cqIq9lcMxwX#uXY z@7CCyWIcpdBYiihzp_sO{-PZooEjju#HN3r27ptUiv*?%au zEwwaWRkmG>C+#rFgs+b+ZMVe~pQe{!Gp6U9iD@yhE2dc4=zJnYF6GLmB}Evyp1;_n zqoSKSt}!x8ao2w=v%_h$DbG9NpiQ1N-mUC*MXwBIw;t4tugR*`csB}>IH{|2!VK~O zz8kE}gv7^XKq{io-Zgw2Qwwbn2nR0)p>&cam2{mL5n1gTf5z$TCCqVBE-?do@h45&8*B6da1H9S-yEpVDmSsZ`Fi7=s6RojYl_WBu%JD_$O zIj2KMfB5y!#YDq&mi=l3iQ4!*$hEwRL@mI{T6R^pSUvU|>_!7qJ+D>g$|3T%1{?8-QS9rTVjAbmq+xmq({j>yYeb*sWXB<qjZGHqr(Kv4jN&IRPyS!H09vR%O!)$d#E_&Zg-5Syt3k8`2%K9N)49dGwY zdM#jjA-!%NwlN2Of{US$2{nZ))40S$yV9#(mHdCnI$*!B&m3->L8S#3R?2yo4w^g~ zcTLHIn-og`wk38HGxx~zE+bPKvX(_xFDqT!qLmp=Il169u3(@dXr~sN(V+!9)}-{d zj=0o%SilTBGL_H_9ts>VsbT0b_zzpl@@5LviX`0j78i ztipes2e=@HO@q}bRI=!zVD7!DWH~wga5!U3;r`U~d7mAS4Yi19TDQ0XxS`fQ|*a)Q{#IQPCs$s=fA&Txg+Boe_7h6x=dJM=) ze6YfLyO1c)!PNDuFMqeCY$nrMrD6|n%@wqZE3I(oav>T)KbNu+IGY>S+oLi z505}8)~sY$J#@Hy$^(d=3i9rZ*=GZ2pv`1cnjVl!4jp%~n-Nrl!H-2lz-Of!e=}uwyjgaA_gniPLiS zivu&Afjw(A(fO(s)>%25&*9WqVGUQ=6v!aPNQ0P||43xN&9b=MCYu`};sXR)bzMy& zj{U%G@c5MfWF$pVvSraL199{g;4FWaQFQ}kTLu@kulM#Rde7^qh#uk(KLC10uMX7^ z+v1CCW5mnUh?1>|k*#Ky+v79)Kp~NAD|XHf2+?Qv=vu}amoe>Cj7jKHs?=L_et^i6 zl2bcppxT4p)yk>;7O%%fOod6q_?*s1Dq8FTN&t6;PPta{CMQ!8P1I?) zG?(W$ZI_t&-3!~2pUfTCe$RVFdHrS$&)szRlScO2m(sH$WhMSmxnod}c$-X3_RC=DO?0fd>#=HNKzrXg_J+I`?9Q^6mH~jx>J)lJ(ocZ9X{wF?8 zI`GKk+)-ll{AnXj-I#E?@OuB}PdV6;s&YiX| z*z5L=%S#{mv1t?U`eN_%3s&D5&?IeVuUpMJuKcvvKI-SBiOpV|QN7-|JR$4m)^1-M zY2es3sQqVqhqc*%-{T9zlUCoJ#0%%j(}nSyAFS85eJAeo{v*F{wW{~}g5Yx{;dae0 zhRiFg7F`}R^KgHg&hP&AgzfJI*)?YV;9A(^lLb9{%>SnU;g)45oWJiGmwu~o>A^#_ zr>)JCZ&)8amAyP;`QAF|!+xBZ-8tuGaQWsI8SX0!{ps0jn+&;qvB?j;g2NX)R@!|> zL(6yU z?Y6V|1D0#wANp-4Kdto1G41Ad7}00@p)U%0by|IC=7|eq4t?160!$2?gBn+KneyraI)p7z~CTb&L1&MV6L?BKB?>#Cd8qnF2i`r^MA zjXTx9OW}Wtw|efV_3G@OTE0~?_2(&Pk|!NG;%~G#k}-UHR@0HYv#O7}Q}@mJNv@Ma ztM@f|!k?6!uV3YLW2>7(JvF0O78_EJw(MDc^G^25NBeAlI-$9o-*4#dCHIf0G2|zC zPmO&~S+<;=Q}ocJZyVLBn>{+GMzg@cGve|5EW>|K!H;h|I`fxTo2?ywENje14>&%m z-=*8=>D{xgO{m$R?b4>cUr(KyzQ+1SxbLdc#V^j<((U+?xz|SAJo9mtvBd)oa|*AP zU&tu>rf2@O2Sa!KO$@>SC%U#0FXt#dl{m1pWCzqq_8@j&vwhK{*exBF9TPpn!~*1&J?@a?M1r;G=} z#S7+jpV4@+=y>q=`@;lY$G@QNb zfu3Clw>^8dOZ7ub?!P*->x5@Fv^YLnZhO46uw%xDU3b*Ju%h9d$;+0e%F9-|c9ee| zEPL!mbY1@aeCcb??0h{mrtA4m##J}7#pC6lADWqVWBi;W`B$s1v&$o&2bom7VOWlU z)6{H6rh2R%aHKM%RALyU^aMs|~_nUZ0ik0O8iG}(&tr3lRdaTBh)Uqa(y5VG z&M;D59#B0GnPCj5eq`qwB2o0me1w?cD97bdMc{dW0FUlXmczW>z0q{3=Y@YLuj5G2 zOdfR*IgDo(TH<+@K$?TFLMw`S0WRb*AMupUJex#RCd@OhMBgo=rA}fjnni79TO(CZN>aJtr zhXv#n%S}Thk8xPZaF`+?9HkV&hvhI}h3CVZENMP;3BIBA z;-Rm29{Y+KLpd6WHG@p68CE<*G+nXjA4YgyE|ZaCR0iVMAtDkb)qq`zJQ)Nh2{QA> zDzw8$gNMVUz7bvy%VptWIRZvjjudMgFt6rJ%9GOLbw-hy1pI$TU&AM06lFZ<3r`g@ z%2VCT3(MfcVvYMz+){51 zpjd69N;)O1J+FTu7e2!LEU!_3Feho8Zy5b556Z7dvP71X3CYSl`NQR@76KAO&@Yvt z7{vUR%tZ-E$#fJ0G?H>yHI0%4X&}*Yk|a~3Ocqz1=1-t=5grTG058hOJn6E8W(*p zdpLrTksgx>1%QD_*Vtl7G0i{|lv^dPjM!oj_!y-UgGXDKQfYKD**J7JW+TF>Y0KmI za75yxJdd(N^@CI~7!rem5E4Q`At(wVQ4~Yuk01(xfEW@4Q54|50JlZ@Q&I5aJQzYS zM5Y3&{h@y#xekbFa3hckAIU^92_6fiiDEDiOv5M04KfoJ4hnE1AfyQ)@ml3)XbMj7sKC&@t;MIbE@N+ItI z1u^F|>Ev2xc0w2qb-R8HJ>(aHm}pV-lej{m0EB@VBw!Ip*ddA*Oshajt?};xk(J0# zvzdQPN`g|4+`iX5lLizFd)WaC(DYsoIvp=0H(!?s1NEGg1@j= zC=`761IE(=6euCKM=X&NJIz@Tb`hkR2+)6U0Vb^B$Tm{Qfa5%RI>w`Riv04ulxgL>en*gEp;S zP|!;nmjnV}g5Wf!s(_8VtJ>`UpY@ z1sQ!3+=P&kfRMx_m>{Mi`pE1c1q-Vh?R5ej5qB9-XeNlr3L+AbF9pmL(uY`;{7Vbc zyd&gju#kD6Z9mzSkQz+GeX`<1kHCKte$fvg4=OE!IHpV(46vYR=ZLHs#I!VJb=-8C zNJt)=)(`tkp)DW84kVRk1R#p3@To$Sg;J3-5s(B)(43e_T1G5;8q^zs`f%l_9F?PT zRF29~IsV@bv2s+7%27EgN9Cv-mE-?3W~3Z#^6u>oZH~Xb-aRUsp47To;l6(ZohRhC zdF+#!zdOdi@$bF^hSXouqu*Emd-m9l)9uDRQ@8#6?(O=vu2(+2*Y(EleG?jVpzw4? zR=u9}4=n7l^T~5xU2l8sv$79o^=MP8-|F`-WCj*|esJv77A584x}@RNW;Z=Ku*$3! z7jlaBOmKfwE5fh z$823z^wo{$(#n5c+Gg~{Z{|MzL4Geo<8B}1&Hejx^{%f7yIvXp$i6o^U-DcUzqb2= z)wL{-e~?h#z?RdcfwAO#?>9Er%zN$g)~B}Q4jBFZYnwCj^8bIG^7Zex7M*3kw ztA6?D#!h*)Tcy-n@$1m*Eh8_yS0{hhuUqp{|5!0{bC0juW^~-UFz4La%l8>Rx-)fp z)ABWqEzM^k4Qvqto3w%v?3}g`qE%AFAH@i+3O2@bmU}8;TRI-`XCk zwlZ(f;)4T5*W{)rY}=UX8PTub`4v^p{VMet61W)Kwr@+A5hji7CQ*MelL$kKR2WMnqHL*)vXep@ zCQ{ZyV++X=QBu~3NR}E|M$tmqml(^G6h(>eIL_<3X2!id-}Aih^Zvf~`@Z*bH*+oL zb)LuaKmN!6ocpBkN90DkiH&o2Hg7WLm^kNWvH77g4POHPI z79U#cQDs;9#GG!h+0k?HndJBFiaRIydejVid}Y!QKQD!{_WYv(?LB^+UzaicLF^}+_RTI|7C!Td z7tcFiC%UR@z2y%VR<||p^{vYMhV|SP`?6crN%33Xz@=Blikwj=4b;;c#;z#90IP_aEPeC36TLf=<|Z>d*xAD(|> zRV7YXF+1^Rdz1SEdQSd)@Z6KuCdXc8jq0(W;mE2>z+v%4`GazX&ImrX!#-^LsMsy5 zTUgqNgVrpSE1Jq5j~FoKbj-;+HBHMHmsjTa zAqQ)H`VRh;I#IA@R_BbOT{$C+Uv5vXy>KDjGHs|Qbtplb{stxORi z?-LZb^Zxn!o4ck4tqiMIP<3r{R$*36^o=N!(&)Z>D<`k1i2mww|L*;b2IWc+>L7O^UWm7oGin{BvKU%kOuc3!AgPOP7D!(XE$%{yzI{ zQBC68!P}mUaM&wtyHvkk*Vn64vhKI!N44IcTV2_?n{2{?gtn_VdBg2DTkn=V-CNJ` zcx6!C+kvZkY}(kGb9rTY`=@A*?npw#!?rc;8K_nmx9f56zz?V{Jb*xb9v ziXBVaMLNv!_WNSi)~$cu%WGY&yPe-U_SQ0=o3GRJ@A&6*4;+o zQM-|iy&`KyEGh2Ld*ssZii$qjm*0OlJmgxIp4{u%jYAXnKbic=d6ZSb#39>SwjSBI z@Z#|F{5JQ(t=49)KcLprE6#i;-zaXlZ|lDIlh0QkbK9c7Vs?MQS8l=b2g0q#?c;sl zH|jT`Xi{gxH1p8t3Ch$uVFBwT^71ZU>$a(w+p=tT3}j_HyiXd1Tx@Ube$3g>&+$D38=dPv7pWSQ=TB zu(18>WBtl!MUH=5Zjs_Ta;U-ktcN+jxW_qB$EWX|^tm)Z(sqTDa9pFhHVI9iP?P4K z8B$e0AZ1T{b&K&ij%`;SO3B&TK{#pi*usv%eJl0X`w!dFKhO6SSGgf3yQ@QXip%k{ z6X%yt>^-EzMd$BUhIdxI7&|*)2`9gGbMM&=msoW2sR@4=xyWi~-Misq)2cL^f`|;WW{;Oyi>Fp!?DA~F z>@(x5YA2W{L=9M8wPLXQW0h}jy)H#Hu8&&|dihxWYTfLBFCFg8px(b2ws^@c#~5Dl z@ex=j`h^=F7B#fyIRt{pT1isXwmO!J_OBF$q6I&Fk1ZL`*Kv=;IcArtgiu z2c9-OKW_7iv=8ZT-q`cU`7LZ`7QS?E7x;u z)jl9qV$dvfc-`GM9*usI`6?|&aOcN~^=T{A6+Zh@!_%XlJea;&@2vHR{w3K>yjOcq z<}BObesS2!(aXlBoKOWCI*>?ZN>MoqR8&p@D_Ly^W_ud&NR{ogX-oa_g z&Nc5l9@%B ze2k;K;=2D>`M!Y8$u`c-L#TMeW)r*m%{0kq)92UapR*f`4^2(|Ic(=@Zr>rhyyl)? zv$=_}pz&23n`SAI9l7Rrj$YkTd?|1Lmh~eBeSG6HkgK@az5ec2{@l#2!@9c6kCuPg zrx^F{z9Pkc{F^%+EF156S}*C`}z1Q2I^Grshzhq9=qjQ&-dNdn4R8Z+0{HcNEX~RxG zud^mU{&dN3y`gETo{O?ddl+=EcbfC|WpZMS>AZm#?mj#B;hbrr>&&`i#1E&sr7s+q z-HJ;6TDo-2uOmzKuZ;Ta=i`4iDqk8?Xmm0&XoRz?RXvaUu2aTM4ykF?z0I384V{_?E_qWq-u-{&=_7UZqg`&@ ziw~I@U)*f=yR(iCzjjPtk?}UI@6UB7>!+1X%jta3aBu2MXD=(um!`=*HuS#mxbj3~ z!Q>{nz74)sZ67{)O!~~t_16u*J;t$~-TB}YW9J5rr=Ba;C3l_V7r53*`79Od z^Vr1)zZ`wz(N}7+^ugo{y{0{V0;y~fJPlJ4V=9Zq`v(kU( z)B!(yuTR~+t;vX41~a3|mq*R*b9VZz-qbXUf>9MMES3$;_qu;SwRu8n#i!!KzI*-K zt`BvI51Uj}zB?jmVx4f&g#o3DGnYIH4)wDrP!16~Zc08@(q6B}u}wE#UgmGK8faH- zZ90{5eq}YfTZ?WLEu1DvFWlI_@>!c#13t^#b{fcRyy_)C{FwD&;m5u^#p1g@p7noSyELmQ3pq3FQrHyfw6Wc<-P|{La#G&P`&&*a7S!Lj)V57{z_;d( z4-bqFKiOJ*VDs|aPJJI=jSNia9BFXtpz+4=RMTzIqdg3l}B`Jsx>==w&swf8CO6UJI2id!#0}dX{?k`uLnd&Qq<AxC2__Jd`L9fY|>$!eB*lR}5&ZB2mzP1gR@Fw5DBK7)~ z@)2969(8qjR?uliRb+wNkZT7ngkPGtc}txUuWo;}zN&2C9{AL+wi%YH_-tBuS5@jc<^GpcWAwZkI}x=qpcb)<8`Z_ByVnE z^J0HSROghd3wx*s%>D=Gyz0)XzjYIbZCoI_6qVc>@;vRp~ z;szVc-YE9hTl4U^`j?)^!O0dWFXr<@N*e0j8hrVRM}NK23U#htx7eVKz4G6EzRX+F z)qhoPBelmO*O99qWnDS&;?b>B-p3#JeOBA+M0%QAd=vd|qPO!~Y#jFO*Q4R}V&}Ur z@xOPrXTg?{KgMk?+>dV8@ROD)#6j?R=4erz2id@S6!;v zAGx95?o-{KtLsYdr)=G-;*Pk`a^R_()~on+>rI$?3~`n^B2ZXzc%zq-RaV|qblTwFWD4@ePzJG!oZc}gGG_7+aAtv1*g z`JP@9Jgr4(h;8*=!^B`p1aRD zSY8Qy;k$Un$UMK~-IuC;9W%YA{W6{T@MpJ~ug|ZrbWX4Md85!i^y+_z0b62i_GjO9 z-C~lg8lIQ<=0e^X2m4>o7VU0uIOy7g6W85)Tnm`|W)-EbZ_W)q8m8ZcBUqwraQZ>z zXVsK9xn1g1v%T|2zI5zc_sFiGqujk4 z_;KG&uM`W)h3)4XAGv(ze8+*08(NK>+ObdIWuI+=TPDlRcY1&4?dti-d{(=3!PdE+ zL+=}}8a?!$$pxuk-Q0OgT2)#{cFX=5Q*x=DU_tWcrsrN6EDr1NIQ;w9z{ouwM&)-M z1|1mQysFK%!<#LR?CKh}!O7}WT;B)#ZgYy>b=qVy@oG|F`>XHo`V4TLUe|j>|FZXu zG9M29*h{atyRd(oo#D&x?{>B1>Zc?xXt-{g>Z!k@B){32;fE@Z1)S_REaK!_zvL&j z??!j?J7Q*k$>wMOn?^5|9e3Z`cZV>c(T6XGJ&mgykNz2MeyH7*+osv8V@5al{4nU< z!^5+t+%ErqwXI86(Zp`yb$SK9Nv05Wbzr5TaVJRjx?t0grMZe))lz=PmDhI;T)cfo-!n6self3loH%Fh_^sA4Pd9g2+u!WS zy^psKmW_{IHi^t&&Y@DEj1o~vk45=A{7a{cjk|2f~Umh`aZ*4(t8os-Y?<1|Paw`kfE z&m33#2c~01J>PbiHsEA4uf6K!+h*hqKHTz}&Bg8|P1?jce^hw|oJqPTZr)k%eAT<< zZBv4L)b-QvoOdW6c%yrc_4%oe4|Ka#_6hds}X>N9DbxgN__2-IUqIJ9zN!_?~|j z>ss~uzN{wi?TWNb=|--pFN>4U-VKV`aHra7%c)+*-NO@h<|Vkc`}UzCW&iO~N#T_P z`@im)YSR4iWmRZJ^Si_9gpGOV7!k3z^^#wgW`@>S483Q?sg?B}uFkrT?w z8rR_@bQ`M{9^zK03SBVg z+<0Dcj%bh+CkJu$XY;e1kLB)uGL)>k{$GN8+ zoYQ`9<5sh4zw{1ny7Yaxs?am9``Fst%${|ee60>qYgb=A``tM`VPJdxZa0%|^tTzU zTDW+JVyfe!g5y2gXMU=4c;SDX8B3%i0>-Enn@)au5^<A zXvX}`(`#nWOcO`%n--X|_F=mZPOb;XZ)}iV=8*F+%)hu_Ti-r~v!*{U=Nq*c9N;xz zQtp|9)(KCY`nX^I)!xRbVdj<|tFqej*6zF6cK@%Ww>-r~`Iw0_T>F1bSXbBl?wM64 zt50P$QoYUVF~etdu=;~u>wduzt;(E@;%g1p`Kr3|kG_`Yl}<~HZ5v{vQoj;ZG~6Xh zFsS)yzG2^n$wTLQFZsTEK(#}!fgN_38r006Oc`e7y7`|eb1yKft&MX#YPGlio_>3z z_Nzzt8CP|!c5nD{3(0>x`vy0NsIhc z=jz;<%a+w?Y1ryl(F(`%=LZ+6&ek*C-;PsW|7(8c=Y;__=X}dk1Akr)Z`S%|$H0_PrSI(Rs3 zThB}%%oo?0YUO6gZ19k7a_#o=r`kxb?%}wP1B#+wwKhCwlkqKY-N&B&YU{L;7m2&oQLd{S73g%= z?ZumvIOl$CMmT?!Rra zw@n!;?~f3$2= zP<63&Ls3V5cGRGUfj#pN2UI(dyX^XWMrg z{rJ^Me&d*QizNMX?{bD%9C&hWU{mWKo#O`ex>9>va`1W7vpa*rgNpmMGH{%9=Je*f z9aburPAPw$Jg%nndSgrJ}7=_l3~zONs|5BF^^x~d=cJzeXkEsd*&-o z+w&Zcu5A`Fyk3J=y9_=h`HVTxrR}M=-pRX5W){DUNci??U{Bwo1kd)bVjc982DSbb zJpAb?y_TDn1#bH=$FR+y+xL?622>YKJ@n0gnqq%SyT!*Q4VZS~MCDdDm#AJB+fuFS zkG;QUtatWDhjZPVjCyoM|Jn70e);-ht9`kKbBb2Sot}5#!0?OvOKjIPkw2Cm>NBtQ z^YYDoEfj4|UO#rB{ihaXMW#lBi=uyWhu#k!RJkogp4Gbu=|l&e0XtiX0+Ts!eaND zO|N!W&f9(V>fMm5E(^kT47{>zrB&rcx9J^^%MRxhQ!&Amd&f1g>-~p*^eR1=lKA-e zk+c|t<;yw_zfksLG2d*G$^)r z!BJh>uAkd8aB2g)*w!B-9~k*ZIqe#AD75Y@*SMeardwN0y|6HBNxH)dV_x~rg30mI z^kb9v-8E_Ssny+KJ#RCf;6F)y8P zc;Ae@vECh|jkjCASKm6&?_y+DQ=Wg*%q8A}Rxy(fFPOYUEjN+Qyf|)U@{`-4>IokY zuF7k9|7-WnesQSAAzUaG9H3F#fiOWmY)9}5_SgZNgL;KIu&F{zgpLUCw{-vk3$$m8 z%mN?-Qg^^+Vn7Q$%_%E=B}2<#59 zVqte+ixrDXz!n+Sh2i$+#fUZWe7sIB; z!4`vU3oMB=4&;H{fz*bw5*3pJKnJw51BeAXA}<{r07<~sw$OfUaRXzEV$KdaD>hcx zq`-y+8Y@aA*swUDRDx264GV|~2arOHm;hI;NwRH`>m5)NI)Hho39D%{K+yNJwSala z?gj`Lr4DFe5K;~xf9-#O|Dm%xz)k`Q0I&gi5wQWLh+%zObQ^HxfJ_G{f*p&kwH<*X zA`w#$?1X~a4y3sSNORPUh*xAC2Ro!!WLH~gHV6e|Ejxr9uojZ7otV+(z$r9CVDq-M z6L+_>wYCQNVu!XM%|O#fd5uok0s{iI*a5X516t$6FfYR0fnk3jt)}A9eV6wq8LR2L zjAZ8_gYz^Ef-pG13@Mt&F!-M-nui*f#{*fZ)6f~h#%v-lGnB;BwhC615@r53y8_h5Ky;7UOXt3vRp3Mz){ZYZ{Mhmr7)^klG!E2U+Ql2VKtZ>y@n{@sk?i-SSk1+#^uBcpkGC-B33E|PuyWeu_7c^1&RPa zK)}BhW>G2dl~@H=fl+Y};nw4F4Zo}4HcrO%a>x09q#^JYoWmoGLkJgF;Yyj@tE7sg zGO>yqi%|jqNVBDev|^2w&W`0`?lJl*SOI4B;?5SU0I}H+NxO4pAt7isx>^>Z0DK3z zhk$$VpP_WNMwi!9O`SVQzxsyHV`$V*jah7eHjHI_tys{mvfhCry{u7Zo{ z#@NDF2<{7DR<0dP=EZe~MQ~q$9Z?KYku<qAliOVGfFO5}wR~SNnV%)_@@LWKP0&y$`ykb58a_*3BE`9)z z!98$zs5BfCNCtW-VDeZgtPN}-aL8I~c$RSJZZ81kS9k%quZu?TuR%3yMp zR16_9RK;eiu;Y>6hncd(@GHf5)pqjDmh`%#J z3u-T0WvIX!1Tt`^>)G8MQ=lM_l+H$fjJUT0H@RZ)7s3WsL2O}4LvE7P4iW$Zq~9S! zfB>i|l@x?=Qn)*$Q|e_Hi}!$OUn&-5V zCmo@b-PhoKvbN4VKzgOD(uS>n2s`=j761D|LQ^X)IS;kvglu)v=p`t#Ld!D zV4r{ORnqQCNsA}fC?$0sgFyRzJSo#Z%%Q2!KTpr>J^pZ)-jdMgqb*cW(a}hO) z2mq2TEv2RLpGN_%u_Bl&q616GTd)pl@jt)u&m1&&{OLAw2YaqS1mYHd$@^^@O$PjhN%DK0=b zwUmgxKfLo>Q0X@Cj}0c|#h#1H{(c~IzP~b_g+37$r7YF{<27;B(W-y+JP!oW0{L~1 zl?zTU@K}JWqXbaN^A@as^pL~lf|aoA@PH9rJvP;2RY2=*z-9YSQKUy^6l|9&ZEMQ*?f+e zkJ^zd6$m&2F$za0y$}J%p2Oz=M+rFsG8=9HIUFboA#fx|zySfp5eh*Pa5x+YFNb4~ z0fH#=L+}d20)z?%5LaO?5JPxZc!+~O0Fxol3LxS5I&6D?U}qpCpBLD^;Yf7a6^We;hxW*SFS3=4lQBrq%d%l=Mn*g8oAn!P! z7&#D7IzG_*InbbmBw&287^a0D4^V)@;P3@}4$J}H@VXfPabR$n82tfo;fw$-5DYMY z4>$yHap+ioK~BknBzB-rgaQu8If0OmVt@})9<~pqr2T>#6v6<|7LaX@5W|I|!88ts zHXN3Q+M{qb=ujL?lN=`F3yHfRxM&o@OB@hdr6i)ILIh(FANoGt$HyFixPSvC14-u7 zQw#X^d?054Io}EVfo%m4KZs%hgdErw})JCm`@%8jL{(>2cF_Nsu^aY@j+=#VUoM?B5 z0BnSROu$Ued^kAvT)JB6FanYR<$!hmY@*RP-~b;K8QcIG;xL1td^k=Z?(9MGp)Gim z57N-diNK2d0kl9!!Wrm5KCD0o4S@-z>@I{|h=2nFK<;rsl=CsqKxH_SJsAq}6h1!U3Wa@|`#!_c0IiIDEjL6BH}1MT!r9vdsbZ5dvr-%3PQo=|m`mC17qkj6$qv zSPFIn@Cs!wKpDQoAK4qi!w1F|f^-$o_`r3LI`{(EX5a-VXACmPbBr5APNhOrap0NM z34{&v4#t8{!;^3g2QdtU0vRX8FAxA>xw&Dthh~p=06b1YHg1q|KGd`TzyoRoCgKZ! zAWVEGKE#`56Bri6;XuB;93fDe63`0{I0Z5o$_wsd zo&_AZ3+f3aJBAAyHZrV$54waSfUrW5APc)KxV!+}yjJt-i>O9Kw{q!>LzN(SgUk(LOv3B*J-z)Xg0 zA0SNyYELW_;;cG=Kl$$%_45}8bf zO<6|tkBp+=T7tg8x1omFBv7Ulj)y~r2So}?Ci@~o%>yq<*n$LqG*Z~zN@)y>Rrnf6 z4;cnXCSwqSbKr<{RHY0%z^u^gWFkt2%~~cR3yCNVPGlm94EiECP3syNqidiENr+7{ z87NddW5p@ngG!Up0$PG;!CTPg5D+pVb(jJm1`+xZP&7M1EU+EQ2sRd0CyEkN-01gMWg^3T?G0FUxWe_(b1FOB}GU^hKfsn2K+?=fgDds=zNx9k#fh*4i4!!S z>>*k*2@%o+8aO5RMWM2zD1ssxX``^&fP4$+w`e$0jBzqzF2;k4W|QpzF|<@jwga>m zkVCR4fQOQQNto9F&!FN+IO&-r7!uN|>33u@LI*Gs=zxs|=8rF;LkT1)lac)ic^z7W z1fR1cNz8SNMf+ehDFGP?4up(0g2LjLQM91K8>nAoP>C{BjWP@#Mc$^jG8C4Yi~?{` zkRL2N;*A(R0A-DCaBw_?9~wFd7sj1Ju|qEhe1*4v@e_=Z(11s*!b~!@ztYt~%Uc;; zR)l{@XO%HQlA(bF9wHgC4%TQ9ya!xKN0_iW%|0?25y14k5*fqlBu!xbN($=}Y9q#p zjtbDIjF39YEs8*smZ}nLofPO#Gyt8VSm`E{(IA`0)Q3zYqA*iP=`xxKWJLVRn0$*6 z+?OKutQUq<$6s7@rrCW^yg5Yo*63c)KA%4jKu!U6ol z{yH5`f<{oJp8p&+ct8O855q>ubo;7^qFtqsN+`BcM1)x=8p$Gt zXINXv9f^z#C!LdGsGsA^nnuZZ3`4MyB<7HRIV22qQ6e5Wp;#An=OyH!=};08MURva zFL441D>LZjG^R0M^dFHKXga$9%~(uvQ%p(g1Q^eG~EwH(8j zgxcu|B^pymDj9|s$>_w98ly<;DUvJ+u1GsAk?Gn=e=#dE0V1B3(j`M;ir%qfWioAl zBv`hhG~Tgu(5;fvg)hDQZzHMSP(x#rqIBlb5<4pv^5fK3N?8oxnae;CO0&M1?*U{4lX22zA0VUN!Sk&19PpP z6boOhR~ds~k@kHFI{;5fC6FkH=qkW}Kwu-&%n0Uch*H7?5qOTTnS`mubYdH&DH#U* zIz&lK6VZvHBy3!Cn2(vBDw0uOWPji>qCaGG&gez{hSbFJNlnzRN$?Y%f ztd#C#5?z;QXtSbNz!7L>`@@ca2qF;^6-uP7KXz?WoGdxf0Fafk_CfXFmV>7L(YY9Q- zZwX?FlP)tn=Sk>z;=DwEwaQQ`GVD$f6fihrWDdo!Ekjl!iqbJvYqp3`k1l)tfz{}E zvl5;Sl|;stq(p1E4lL=n8AcP)^NKKZBF!+KhHi8ygT0WDW~F^B5opBapLP=zMgk`* zVzb86(1n&nK{Me#O~iCC?SUf+2kMrDHdvIl&`B6{q#p?bf%xx#nxA=BAk&frC1D~( zFsz{wmPTb{V-DI~!b&!>d54~;Nu^9X8*{E{8k+bKtjPqLu{$;C5rJ-?Q;7y(;7S<= z6(M=j=;mmug{kjSHV_&PHuH@F4N$eo=T)anzo$K9L)%g zd?0kF(GHXj(nK5*C?+P>D){RBEf>H`0~qP@krD zi1-9qLi?s6IZbmVu83r8B_fOc27VD+&U6fQl8uMv=K-DjV&+Y{Rp^v8#r(9uV37@i zh?$-b`!*iid^7|g(u7IJF^VSkKTBlIq_m;TE{BN*opp(SPM9cWs7PmvpqCei7VA_=1uP$9$KMQaR)(rlM0w)M$~^hJ3slK{WzFc2F-38m4}a7xg_W6-Zv z;bbuVvnHs2aGLj!_B60(DuJz=-}Y`y+=w(vlxf-wMfV@NuqCvuTKu!s%KEJfc?Kh7 zE1}H{`O?|OXiHMU<`iDh76hzMhKh(DqA2YGEbp+zrcq(qT9MGKD`B@oY;TgVV8su* z1B+O_$)Hk0X%elUI==@pqlIXmrzoA8rpugwK*s21 z#sFe9t9v!kmC0BVA&6v)l&2FQt=wVuYvdV6$28U<9gyPB(|D$0E=BC`Iaua2wP=5C z_`T*hbmT>vUMUl>dk2b9ge>&5i^H2dtr)|WMAUjBy@O!8GmqsQdh^f1QnNOLLP|qr zbU~AU+ZytcFmM4#!9X^m5*huIE`*M)P1ipg;}iL$Wv7H~s~UMP(gan)rhpO7gxzHfb<_VRqf{r;nr*G7Y&4)| z11)1#W}qd}+N_NO>mvG|$i6Mo6vCfm3vQ!-M1+u70n8i%I`*;bEgBsR|7aSojtw*y zI#bM^jM8y}et`&giX}vRPXFwo!7K)iGd4cRWMC|rLcO~}cAx3jR zv!U3KP$Eq$mazgri$*prI#?6wNL$??;zFz`T5&@=!~EhSk}>?v{FsUd!50LKFVh}> z?AAhOZw-15f7Hg8$WQ#5a$zZli65nH(&TlB2eZZ3p?nQh{Z8nhJ~X3fM#BGH@`YG; z>=?t^l;jv3K0XXQeSCZfIY{sh$H#}<^74UaKJ+ATDkGOZWC@iItVl*d?BQkd*as%| z^6>$F#XIoO3#Rn(fQ`Euiq%Hadgkw-?HX;TYl; zd4(id_TNf{7AxgSS68K6E;mwtx<;LC$q=dL3JRpz|HVn6GAqKkGsL<8b$jGSB z2$BV7g+?au98o9tH1foI(GcjRN>3OI_%niU#FwT<$mjnF(dh}WE{rb(6qSKn|E<}m z(5O)9SqK3vL|iK2)5x`d7|IYzgTP*n5qFixW7R_yyOv>2n4xm44=4~b4+U|}@G0p8Yrw1PV zjeM1aF8`AHbW;c!f_Ifr42XVLLbpIDgpIJK3YEoj@E$U#1jmfv92wUWs1MSE6o$zx z#F28E$fA`>dK$SaFkdtVS{@y(EG`C%3BSYmXfh0^bA`aey7ABpJmFShyj)ofPvT*6 zR}&?sikL^j7!3t~3m>keq=|hPP8gT0q(NgThvBX;Oo>ZlQ;Bvh!*-*@Y`YSSVn$t! zOdvx_6J;{GL0*D+=f;!11H@~DPPx)0CHKV1jiBdXZ;|6%0Qf@W3D4+4L@8ujuKc6# zXjWmN5!!`7M*HEb(ACoj7b$ivoQwPfycAz%0zJ&g1Qv~dR)Vq7P#&I^M$zET2Lu5Nam1uy82&qO#=oN#6a$~r9TWrpe}Q-m^D&*{{e1A0s{V$qpFglIKY4qAx6ma z2iS`1$O);@g{lPJDMamN0tx}S)d*n6kRLqsbR~=@PsYe2eEypdL2@CXTwpf2gk%VG z>0HR05#557p}=mgNVQ-`G(^ydtdF5}HNjq7Xhf=i2|gf;MxkJoCvikBhkgUL;A_gi zQ8mz_qk)=D3W)H^4#!wd{1;mraR7U3ps2U0xbTty9E2wi=pz1GFS^d zj>DmUW>vc74+DJ2V-g%DEM!WyjZ5%x1X+irP-ao)qYCm_g%k?v9jC9VBI=!9GL4?! z3~oozJBJ9y=!h!($H0lc$CwsD&l~|M#Y)q-iIw)d+h$c-){W5kNqmcl&>0y)aKu_n z>L-E`-4V=Om>AD!;ts@s3>-&nVt@|wVVj13I|OL(O)GCQA;JV*)P@Coi=gX*xj||W zW2i+wmRVIqgysc}y=GMilq%K*pKT5fMy3WkOi>#{*~8 znKVLMP4q!1o=p`UdTL1(4$x+kwHI3#GYwhrcbAESxPQ>Sp;6wXlMph}3A6^-bZHO+ z#%U+iwG$739sP{$leB|cjl)JXg7u|;icqbbu9rU?v^LTB@PWXTTtu+KojgIUtaS;_ zSZc&7CgQw`bp()&Xl11gO+A{Nm@)A*V>~C_m?kFLUQ#DW&cH9q-$M(GPb~Yr4guPl z6ljVLr_#opJSN&llVCQwNF8Za$ZMKO%+LrU7WXo`9GQ7xMzRVj4qa4`9EhlY-*7;l zpnApf*QFw6lo^SFkq%aTNX=<7j>PcCglW)%-=Jw&!`WHWejly(syhjXq~;S8VStj2@hL#s5UG$K8yJpEf<4KRs_x zOxt%&T)R#*?ImmU%y#-VuXC<{8>G2Q{bId0F7ma>ANp!YMO<#-=ePaVj(-1r^tu@v z`h07@Xowq&N|E8tNl^!l z^gDapKT{ABKQArYHfBob@~oq`yy{Gx(|&xj(C3Zr^SB%*f!#5II@~mW^^HG2D=D^p z<1ZnR^;J0)zh=e79_rP2YKJENv+F9l_h^;3vgg%Fmve8Sb_e{3)P#sN)c_&B%(U%RkppmXxHk_gwFkmHf|w+5KCXxBNu^v>Ic8?1wB zCcW)abaYTpzw2h!de!HDec#mZ3pmvq&W&AsaHiojo9=sFUR<)=y7IDJTb!`b=+0;fmb zoAYkupi>sUrf1bZgJ;GToEUpmG$pCUg_-F~?@n4(yR{KllyvET2_Z0>l8Sx>G#>UwtnXR+@req z_Er64s}Bo{BOeW^x7+pZON*wnZeEJLV8IuDRQt~=?0mvxY5z1uM9$hNJ99rw8^1ch z?aIsJZ*!BQk6svmu<4_8O||Q+X>+MYu`|{UndP8&xT>*TLH4mOxsR6}*z{=k=@*u5 zqHgcW{t;~+l$_X9(QMlW{ZWg`KFwcv*6?ND6NJreV4D{gjY@k`dcDT>IU9_ry9S_@xcr zW<2idIG4}MtolxjFzu&!&OJ2X#j6Sj^GaFKgb0&TqgPR{GyPoJH7p367~kTfc+sXK zP4c5YdtJ}!k@R7|&$?aw1c%8t7YT0{T?q++>v_lhA$vPbf){ibg7Z) zjdkplU1R3(?#kK+{rvfpwi^C;V_Q-8)}lINxS6wmJrin!`c8>FQ}4vBz3U>kawmGd z?sfM}e$$n|TF(65e`vF{C%>n;25y$@I?&#BkmIW0jPYBi$v&DMp0Xo)PC#g-%BhL- zJM-2YQLyWfX+dMxz0{BWs()=xmlkHm&F5A=Z97PMzw-2(yW3XfIE=Jks%QLgt)U>* zVphw4)s>Cky>l|{(?9+Bw(H`x8Ck2xPrG*HiuKAV-`Cwx>92Y>v889x-Fl_gw+Cfh zxusrId|cf%(^4MSFJC|5oyV@0+!jvF{R;DAwx$b=!!3g5r=*^;u`KA*|MJ)a7pgxx zT>h1QpVRzDu8n_{@swjeQEjSSRQP zjLprw`)6%Bw%c;)_8Hz=T}Cv0ulAW`^m9_@H#4@EtgY?RQMza8h&;E`0qX<1tb8)K zUP9&a$iVH^!pvUZR?N6rboEllty8wGyBxp3lXK?L!#4h{K5ReyWBk`^#s}uxUWw~} zd}o@=WP`%}+a49IsmNO0=*7dv3s)}@UpuyNM#NX?sZEZBiSIri9{VG|Va>t$cb@&M znVDu|bmCLv{F_VW_xW;r|2y0DGgC6|_&<3w>VSFM%5}enoiM%GPL{XdKWDAQu#(%Y zIv(yawq3-=sEJ-zxAfSw)$0ARdz<}#=YPCsx_Z)F!{E52jbCeK&K%-pmg{yxytlCV z#YqE70x}zpn^X7Zp79sWrWS5%bj8#2uq<`w#%a{+Da(I7*fsD-_nQT0o8Jm(b|mIm z;j2EEqdX6rHJoz0^vk|KEl{4w^@Hq?y63zX94wQgpLs$+$E3v z=DyjZ&`WP&m7VY4@nc4hHb@(g&}^^R__A?S?D9GfP3}KATG9T+&s#%h9awv@`G%}Q zJGW%V{p0S*jD3EaJ979CoA)Qa+bMia3?DBuy}$kb z_My36%I)oi2M*o8Gof;{+rH4^VXL269y%-TWH-A(#Q8ltof8Hwo}(z5p8k65oUP(D zGcLc53)$D@Qd+;Cw{4G&K6l=sq%8jQ$R(osO{%UMFLZMs;P~Av=F+Nv3(q`D$`xPJ zJa(QLdD1v7?QpThWT~b5D*4F5P7jXUuNx6k__B4wV=Gigw(l3FZdy=(-L6^n-&+4F z?LVYN%&J7m;FG@^HSWwYU0krXut<4*xcZyk*M}GUjWW`Cw%c+Od~Wuu(_4At>gvWG z?>ZWfY@E2$a>NoJGw)7+AwRn8Hhp`kFeGu<%!h;STeeNTJ+j&0;Rjn5+q@g?YBlp) zuKClhoNi-_Z{|4_Z_c}Z>B-M`YX&z;NT~FZ5MsG{3v06dbLll*UM(NJNsdK z(ujyDyS9b)X}9A3!ddywYnRMQYEgfZX~Ss`H#HtIJnqu?XKm(v`m)^V(Uvyc)w8F} zd7FPM)OXlAs`cua=87V#UVl^3QI4wmEVSScN^C7?c@~boG2ch-B*MevX9QzgWW*mCm9P32X{Vj zO@PB}dd^zVtH3A87Z_5sr;6ce`h5T~Jq9Y9Y|`kCRmECRg=Gc@sSC0bLge9(N`Ksx zu|Iy0S0m_uNw?uOjEtZ+P()BgFuO%k*|ZmF5zq;>Q^ES|yX0>h^T3Ra8Zix`%WU0f z#{1(AErKO++A^$VvU@kfXlkqnokN}j*}*rViwK<`nP5qvKb(-z6MYj^1FTD0Dt!kU z3JC}%1}f~geIk=FatUbsEqRQGq*CcC=qhx^@d=TCC{=_aG;e4s5--36c6u{9)MOOR zzki5F6;>pCF-6MYg6{JeQUY=hrqd21GKtoEJ-Yo-7!`k&CGVHs*#II3OtxTq7=wQ3g^ibw*Nf2eK$wz;UoYQz4M&BX;%_P3a(GQdPg zocW(MXrjNh8rIL)qjphKiEz5dKh5Qj2L8>SfMO{m%tamMVs5Tho115-)#{8t$O}xF zI!qlJrcV5;IH>a}h`dW8ZgbQbE*fn~Z*%^C$=rr!Q0lO-FcQ5m@V0i+8*}x4q&0`V zsm#b%hf*$TT>!YKUBJXp7fP+$hiGI#(DF0PQGNJRF4h@u)aGHsjS&+Nakj%FwrUTD zF?Cy8tNsfKUhASZ|95uy4D&EdjCF>3xS9%6o9C-*=!0%8HUFjqd&a+OuKzjy-#-X{ zW8;bD>wo(SZVdj5t71kiGOAwEw}#Z zdH?c2a{ABB@K*=v5C5*ow7Z^v*NnOcBo07R{+_v{`w2OXB3l?Z!E_KAv=kN7Ba}g) z1a;z^PY;R4$dbR~L>@hX7mG32IZl+_=TsSXJ|#W4=y0JU_>s&XBb@FKji2_ zM1aOe)M`-0j4~stM@BI2|fp1brZioXdSrl33N zfIh`kV~`?3ZU zUq0tF9Z#&@uEH;U{Gg4>>Hr^4i03pW;egEoe7ci10}M(EBQy`UBDf~x?aF#hjs#c> zT{|*YKJ7qk|ET%XF%+zZF9hX6o!i0fM-x_t(-T`rjT0QVMdjNPeVI*$_S%&OQJj6KM2fu90gK=;rxd!?jmu zZ3C)B?x43YufN|fg|E|i?nB&aqhm&m6UsQU*sJ$6+xqk-{Sm=GDwaIE7tgrLs2wxt zF60^`5{a{auV!Y;(99bNe^{#(@J-l9;{_b%&`!cI<9LDMfi4V&5#edgACEf{(gC3_ zYwuyTkcLUo1=E^+z$4Ot5Y7Fg@pqcy$%`iq31t(}-3y2V@1S^T1?z_s#@vk`nGKjp z8mADXb|vVCYZagkzGxUY(c%}27!uH*P&YdK45_<+88bsv0SYf+&l(#1H8m|5k88}o z57O~%!wtexP=7oan1gC%%21>>A}I2JtFS}H68q_nl!FkY>j406#w3t9`=W+P2kF$L zxL)jBOfQ5LOq#cPBn7ki(RaHRp+~RYcf!_!^^7+U)^vGt2?DvCrq5G&Z!i2=!e3{+ zpo;;2_c;W3M+xws->IRCp6;VZ(C@1|wN;2m5HJ;e_TUf(Vre$h=uVeIj7#h@wjjy7 z)S}n@wLw~l%>{XQG+c;DbMbS9(;J$m1G3OD188^@oXz#aKHE$OyLC~>96C=opx!g5 z)W`4~K-yuqF{C&u%OlzLMRRv$;{a$jC7;CL7g5wwJ*a6UQhd%~Rqs5^f02Ya{ z9HQ+zWN9$;>r|*YM2}Q0^Vsv1Fs$q$Y!+A!}~#Vr`A3sD}CW{4VZc_EBmzv##1jO`5hE2^AGbzBLtwtV#HF zuDN+FtvG1cXy%}a!<|uu-sD9glJ-OW*buuVNp}HNcyOct*^7eP_%A*5)AV(?)Kk!F~I1%FUE1p;p3h1r8Pp$7tg3N*#C!@4Py z=Y7BLlO9U-hWoI;qOFaqQ-kd?FVl7WPOG1^D2c0D^Cb1B3+OjK;M>$ucdvwRDwIoG)KTPTRn z#uOA$sj=DT@%Vc7WJd53kk1ohZjmc;Ax#5G_^TPqAW1H-u*@s*!2~`CdX^`Z-S`8U zn4FTUaf(`_G~{FIJMs7#0;+mW9Pd)<0q@zzQs3dkO*5YS_vzSwoxPe}Z=-rr$~B7m z43#!Joz&VCv@6F*?)<^O<|Na$GzM(0Kwvi6k9pT?hCr;UlZzASd%_YtC%>!q#>C}f zA}}10-=%*Q;&lFTOgN9P=`g~1c_wS)Evs(X!l@i{xTjc$fia7(-^$iAA!GjK@5j=T z|M;(2E{SY1A^rP*YVt<{#~8oHxmIH3{Y)1pvLo{XTjF>ko##r?EhYb40B7;_rlri{ zCnTTBLdeC~B?^D9EQHwyyMRwzvLZE2A~dr2PrI=$OuBx@amK;K%>nOM@l3qB(GL+% z_Zyl?9&@*!oL_JH^=v#5 z48xf$PAPbIXH5G!;w4KPC?I6fQ$bVW(_9Mw@ts8TEWQRClL`Z91-Z^u5T8oWi&wK) zK-}!cF=c;D%oGdBW3NDPp97fJu|(XAxPbnBh+N=5lAVjmj1U)JiYnh7#OaQc`) z1!T*P!O_Wo*_?;F|u~ON+r%I_e=Ov(s1dd=po@u+mLf8yynsifb@vJdu?I z_@Yh`XoZjo>IMk{Nmq;U37|g;qt68mWszko?mHu zc5`agR0MY_OhgXQ--I`R^;-T}{pWAviIP!U3XtofPgq*PY zBepe<|Be`jNR3MES`)bh|L+h2eT>-bM4pX-iJa;io{85pZn+zsiGo0;S;a9G66|2l zge4z;E$<@yV)m=??iP1Z7tX~g4$V2FJHYcA2#E7%V>B8nIIU~*tp+kvng?E(q%6E% z*X~-)p4~S}&EuMzp2x8!wP|@=cy`Zgw|WHqfdinW~131^c~x5IaZf*-c-qUEW>Rz%yt9T*p_RUqy-^= z`vy~y-m9s$OyXq zf!8%!j$_&VzF~Mh&AoQdvId<_uide1yW4j=4TL?m`vc3d2Cc5Skx53sgP_tL4Y%iL z$V!22I~L@JV+^E7c3rI&yMu1O1rHs6E(#OgbQ+zur@`siZr`-9X`7l)zwPvlzS)K6 z+@2dsd;@ikY(hOa-5+!<%jr3A(RKTFn+V(R3|jkbhYCV=45z6I!fR12@&SUe)0SFP z9w1sRMgun`n%uI1W5?;XTe8i29ZND8+Iy#GgXvmbW^_sI?m+%I0+91;vqRl~-nAX1 z)svM?EZyyN?2c7ofhN#!G%yVhwV`Ue)pQ50RXIXoWcKf<=01YX1Y@n{$10VtF8@RAHxNpMPFg+GXA_mp4OJY$1vf)Xk(6t90*X=^YI(@r0X!q<^yTp3K zvKwuQb>z}9lrmEy5+QA&8oI7yGzMO)+wfHR5QjRJrB))gGF#0a6`4(csg#P@_8Ogm z={RoF?R&1_b-g|c)%HBQio!}Ew`o^o@?)9oS8O$7+H>Jt$MB>3MCV(XiciyJ7X}F>kr14aVzr`<8@G$7r|P z6#2f>^+3diL+I_=rU`O)+J(xV9T~)p z-z#1i&kZ5PvLU_^dSa^^30d?)NE+j!FKMho=QrDpVaS7tDCMPpOhI5&D@CcKav80X zluQX!ppAg^zF^s0lNZKw6n+qtvRUd$tz^aR($y%f6`STLf?Ap@pf{e28b9CCXe+6y zG4Bn@B{pFZFt2`6Kv`F{SzySMf)LeYiaKf~m;&0~5LM|AU@Kw#L0=zRkMYkWJTud!(?pdbvA6L}1hpHw+&kq;CO@txAZX zs>%&PEk<%cHLuTw!eYRSG2@h4`v$lLi6#whArwU$&$s%EK@&x_e8ooWhzegA+hRx1 z<_5HBAObuDLS(H29EQ zkuk=0GAaju4d!8k$=(P6Xu-T9C$R7_N-YTkH#I7SkL-U2D^)5jFbUQu6^|}*c@aj% ztuDcj8$@<-f8AgA*Zp;W-Cy_D|Me>>@91Q*d4immH&z5;05Ncm`?dTD0u-Ml3885t z2tSfD|g}99#6mS6`~%NEjM(C@gx0xQwtBEs29gJ!BB) zc*UEOIIrwnwdPz|2W46_HG+2C9uCD0!LA;ly=XMVC&lh3Ld}L4gbIdiQ_Gsa|4LNW zfY7{us93VTz(If=F?t*8boAGBgsBIRe%%bh>4ol~t|Md>dVh^XL+?ZGbB3VzBgp+* z+}7LIeu!WPM5Hu0Vwjy*U#~f;avrZQ-*hafcQtlhTUgz7cv5}WEu?K32MrrF^be$d z>x-5Vs&B^sd*~ChM9?P6`Z_P~Z23jfeA@(nweWd$Cw6(QRz`|(oQd>nUVbffnyuvXc)k&9sph*EX?(d z46HW^(hH;t-zL6a%(2sEZew~j<1U(}Pi<;z{y`dqR_1k-AB$tkXX zOH^7^VVhd%-?q5MB3Sgc5t06n3ZJ+YgmfExD_cd9doF6pt(j0;LRZ=rTW;U4Z@%K6 z^{xa%ER^ENk8mC#F8J+Bu5##CKC-+ZkjG-WyP?YbwQ2!1 z4{Q9-gWr}I+%T~mA8mysy1}iGNReu}k;XEITQ{)EWHOeu6^vb*k*WcbWqMg2ruL&4 zakZh<>NZiQ4ymW2Xk(Gv!rg3t6V-qJCus{bUunr&x2aq27K;o`> zXH`?2SwM>Q7%8$frW1E<2!eEBME(3i<+uj2b;(WZP4Xj6WV{#2W=(6+`NfTk-u#$o zhIWV`#K8>^;yiOs(JM0*-kAnRDSCOKKsP?JMS87>>m$knR^;%*)iq1EXQ>N|GEKwrTR_HGgAHAzO%HIrs59j;)3-e@uh&6NkG*pB5yI@EmohL?9AcBf>t{5_x<^QP@V{UKbNt3b2uCx z`R4G5jQz@|PdqMGI|%*$;e41cC}6{(@B1=#c}`CF?G+wqJrj?M;oc>JwwIsyLo$4L zg6$SQaEB*=V|6&ha)*Zy`aU4FcVy17GMx*3(_fer&*+OeyR$yp*~$NLKHS-lG(Yam z{lmQznQX@)BUY<_%aaoV2R@?QG*>?x&LPZT;`-uf{*dAg6X(MZFIV}IIsA~O=6`FR ztcDA7p3jli;lra%>G5Ze0@C(TN+%Cj!#BgdLy}siIRsw!SN>wbVWZBl`t|T3?jIr~ zris?FLf#{I0-xrG!-so2fZJlV`hsxt%l#GQ$n=j^d&A*>VhF&@PyEHs!rb#|J>U$% z+VK~heSg;e|Sk&t^5FKK$gEx=6l%25QhX$?20VYNY1`53u*OcxOihOhSZ5n z*m9EZuW|-6XVbs*sZ$=fBgB;2_kc}1Gs@>5eGm2D?eZCYf4zH{4mk#g5!IbY87y`VV=u^ zZngl=VXHFzGEDgUn8{Y0g%5asQmA)Xn5b@-WGR(q6(*tVb}2JG zjZTtHtTHJMGa{t2nvd0*y4NJC*B~Po)k{{l9JJ1|behUQIvItQ7Wp#Et}fY#YL=oj z(+31eQ$@n5f4E4IVdV63l&H~_h6=%Rtjy-;PqB0sYV?*vVIm)Cl59EEjz*;gTB>T+ z(3e|erEBNpuoPhC7h$0~o}~TCdbb`(%IxB8rblOtmr$ZT&M9xxBX1 zi)v2kaWYM=xKk%nd<0Js25FbCjio0oG=tggS1S(W%l3qpZ}4qau*fF$@_+)DfSAqa=Mg zQmb)yy9}qa;<8jF8S0yHkR>!)mNaaerqeLX9t4-!l;?j*LNwHp<+64nQBiq34U9PnA z>o6pj~F|uFt?;=v8Cy=f9yu<&?w5XC5Pw1r-7bbPtCr zIAccOnRWP$3N6X8>6GEO48x~LMzKtxv9ffOa5AJ~MEF8hjNxb_fJ5oXwjLlurbUus zN|s7Ju*^d4*df*mGK5TL^$LU-*0XvXgwwkEBm~tg%)){tz|#lW5}>a7M;4|xm3UIg ze^e&>cUFW&Rd*5Yg7#&8W(&Zd0VJKNSW7$kvJE%H0O=K`viz{j@MKO}e3r8dQ zl`a#~`4^g3Stm{*?}{|wfQ5-ve&I4ve~L2H%5RijTxe>WW}`}yUZ(`Q>5_sHF0jm1 z?mcixk|haci_3b7-KLrVn3yz)A?QhRi!ce7N|;QUe8VX{PGJj09HkS6qsMG(f5?Ow z&|2xT%my{^V`?9C69s( z&3!-zim7f2Xq>wA64eyKB17Dkq3o?mRwR$57zgwa+GClwB3W{ACuuS&iZn}PDkY6^ zy`@3M#ZCP!`$$Wo>}$HVT0Iz%e?!SAR8cLs;6v8bMMyp6iZ%5W1$G%ij3I58it#u4 zbxQ4&)>nG4fJS3Y3eyXvL^E7PLfSp!=6xx(djd43sI3ZVBvVuf6k@_M4jy8=G)b`) zHm{d=mNDyHA#|e<^#h<%c_%9W7+O2rgxTXH!B|$qINYR#6au<6!7N=Ae~B*SDM&0G zsiO|*)Q!B#rc@)DlohOUh#J$Yu$Y3B68$hLO8u7I8akzM!>eM1S!nZ&9=J?$In7WU zvTPBiaJ0C;aA@?>v3XH-DiDT2?&P)@#``^$1zn{-$nBz<&@XDz67_p+X)6 z5JC8SVGL}Waukt+_(0sK6vPN1<0FU&0474^F(Lqt9nTKJC5L)IP>56n_MqSoPy&)7 z=TT@%rQj`se@G!-A|bGbLTnHh)EI&!1+a*ud~jF_Rie-iObB=f;7$VPMSPx=FO31t zAP_lzy$Gg>0xkxk3HJl15k7Ddk%Fx6MR`2nJA@ZFflB34@QETkIaCX_^Mr^Ongm@0 z`@xGC5Iaw3ECgElNDN2<2n6LDqjDZpTMzsKyZ8tXe-H`bfo#e7zzt&<6Nnm3+6%S= zqR8t(R$;=Wa*BUI2Tv{nw1HkxF|P+?0-E50h{qSf4d4NI44_9K=_oFtG`)DjdgwF= z`XPh>{I3UU_%Wy)r6>lK@KH!io}33$P|p(q=XrmN5C61?zH-l8t!0g3Q4{ZgQ1uGR zRjRShf8zf)d$>yYnhR29WKmv&wm>bya%EJj4@!!-P3cw}2MQ2)td|tmX#*;a8cH;q zfJ&!`jX;wfUsE3tp+bT727|&Qib1bh{_EpO?V!>@Dch=o?%Ge%44|-~!hoz$ZH29X zda!0M`!i5#8O3zxf|}58^j4HrkgQeOETVo^e@gd7r45$n^bv%nW}|V*sG(XJqMEC8 z{jSxCPIRIZo#;d-I?;(vbn@>I0Y}W?a0J2Xs~iCb{!;h_}^J%c`a7PLyZACJh4WA z8bFJcAh0n~@kAhCTCs9wD7v?pLo-^eN(!ruD^sJ$fS6Sv>Ksw`h*2jNE7hJH55xz&hWUCv1Gx&a!yY=si5{C*!Ks z9osin6@#x7Xxwkr6Yz$$MN4QF8Vyfzf#-y1w0mxddUdkqF0}E@mZIqCA<9LNDUJE8 z`HeOm!VPJx8`uO!xdLtTj>1=_p;qlG!oDOZ0B^!?&evKY+lq>5N}{Qie;HQ^F7ris z>ku@$3o3ljGD9_xkjWTW*+D9YESJ^gRDFi35yePTLPQnLw{MqRKbdlHQ>7-ODZ-?P zsSBCXBd(0AXtRu2)&#Pk7gRw~;b_UqSLco*axN3Q!qdM+SlXPTkts3NOi~DIWN>LC zu=GP%u{7zc+B`1MiB5F#e@($KWyRt+fh#Hq!$@3$V-kWcFASr1OK^$W)e9$ajCoWj zq;G8`K{Oh`I0g$8gBvgc!*Mk?a8+kg^AD~OIIBN1_Hc~E=m~=TwTr0)Bh(C#7_K&w z2^`0PbH$iCOkfdXrnSN|#V`f1Vc@M2JMaQ$x}yXqNQ@w$votoee>VVK4AbzVR3m6c zxw7M_-Zt4@6$n)Tc!7}MnUG-6bsVgGjRkCwa8@JLxc%S~_8}bC){iyq0OQ3ZAW{MI z0#XJqXe`VkR!%~2Bsfk_SLP)~qm7X=B`S(fB|AXoNQJU;Qi7FYq@;wS^@2eHfIwzI z2n;PS%5$vAamx7;*Ns?4NBn$+ws2IZczl7nW1lG|Dte6BPLr_0yfK_mkfSABs69jY*r!QfkfAAo@U=4z$ygZN+0w*Nu z)x}h+1ir&5l@b^oD9i%tQ(3u6aHtxhlxV9=*uWSkfPI`s6_?OwB`SVXN~o{c8;0N@ z%@QU}<#c|S9DVB-1@X0C6gXfnaEdI3%%)<*zINACOC?iHWI>ZcD9?(aDMmDZyr{JY zKzdd2;S6^mf2a~z5ti{Nd z1xQhpkd%;+{9mOq$?B~VWkJGYY8hAbVrX9JsSIO=e;&b9G_zMRt1uRa34*~#qs$5v zV;!SnQ7LBag9)PQLW0X1Hx&sSRsb&A6RNrym7v)WIMe2}S2qSX6L21b)(dP<e_5J}&d4xNaTBc4iB5F#-zSke(TPrU zq7$9yL?{0}@?c2e_>|1*OmQwQ3|KKD*dTwNyF+$?8$!1~BU>x(w0zIS%&$FUIR ze!7Ho$`7^g%uf*MnTYP)Ycga}9 zkF9%@viIo-)F zNAo*T9X)DM-t4gf$-}O;apZNKk=BrAoii}n<$7m-`SiY%&TaP>nRFi*STk^5&xX24 zgSM43i2*&DZF!k+thL3en0`egQo@|hH9LQ;dqt76w8t;OT@Smv7#=ouztEwie}`aU z<%kV?gNNi5<+%GGS4}4;^fDWMwED$S=bwFx!<^?VIoMEVJEUZ;;HlT(8y&d%hxZN(y7ufkqI_GuugT*(0i^@KOc=IoJ=gpDCCw*vd0SR7 z`j1(^#)oo4CXN|x`(@>@d+j;De~9j-?#!EDjqT0&_-N0sPX|r%7JaHqIT|NU$P|Y* zq@!s;hw{2j-f&)cY0?a%LknZ>gxoSYa5wwxAdek30VNx=XWb$%diuP*kbkLF;oRkp z&U@3to!z)Mvxc4;((&ZSz<#C{)`jDj-{r4--8+rUsmRm-VGuajC|B$wC^T=UGeqHlAP zz4{i9-7mEZkBLN&pYZ5ce|una-SVfqPlw)myy?NQM0>B;+>ZT1oyw!$?Cxe!J)tf= zy3*^mRcAe`!67Xs?L8`pkGlVR$msP+!?24?w+5GrW=exJ1 z3rinc*4|Gke0}Kbl%nJvcg8#@^Lk_#{OedN%Ol4Mf^1u5bQ$0|f73PSu7{qDW6RjA zW}QD+N7nW}7k;~Nm|SnC-^U1B&y^=z8MLuJ?i%>^OrPFXqU%M^cC5RV+hg_aUjy*r zf25nVcAja3%SJgHn8rMu{)2DQSoe858*p0#m-kD&ZxpUuYEr)MvunDg`)|v-&!4zE zGh*cFnxxBFj`MaWf7q403mP;f!qv&1U*12eAZ$nNrNK!@|<{wT)EJ!!SKQ!pV0lub0!Qpy(aRtjhETl<|}=Qe0P{5t;D>RQ@VtD zyvZ#2W$vf`nR@NSvV*7l6M2^pZgB2xVCX&Dqwmhxx3k^}fA?Lh&v0>znXVZ-_H(;xMf zJzTQt%+Q3-e_`Q)N8U#G?&`VXhbklosSwNev^Y zeck9(^Ng7m9zC|d3h3}t&Y)v!ri}3_i?%8+*=+8ae`Yr_DZRQwNoCL-t~5BwXU@sp z`Kcj}{hsek%DR|tI^l-d$N1Jx8TXtQR8P+BYkD&2vC+t~X;^eUT`&!h@bLxNT*nzem^wIo+?>6Pz4tvf`O|tA760mGK zHlX1EemIx>RjF9g=Rt(GY&*<9WULUI+F?P$k zE&kbbT6*&$i~a6aE)&yxw%vZb^Hf{kdi=_|e}Yf3t-VgTthRJ~Kzx25T)>MnOOO;V z9bMHcqg%ipDSqC;#~}RXfprEZ$3p)|5`?*K@dzn0CHh9>hQ;lQ5SZ+h&oquV$+-|& z-K%Brh;_GzO}w31K0f$2oABSA%#ZlqEFJs7GVJ|^&bFLGAEy2Ed>p@Y{9zkG+>tBd zf5eeq7Q1dl4n%nQLuab;Cy>Y9Xtx?I+htG`NK4ZK+BlolyH9IrdHR#pbrx|YM z!aTQPcZM9H}=e;)R^RZE>xW#(1>uYd3K`t*wIGIaDwjGy6k zWmR1CxgWN=l@5;VmUi-f$_1o!&*S5=FVXlW_mcS0Uwpf^IDk!>Y;?*yr|0B}Q-(hg zd)oi-X^P*XH7({;r}px#xb+^h!b4re4FgL>YtvpiW(P~e7IvLGtRis zh}Y|Q$L?)KYPZBst6CfGVj{Tx?%<_v(!MP_KmO>zsVl1VIBVFVwExwL znBizOUTQMc$#cBbw2i)gG4Bk$mKL5^-e*pPaAMsI>|oW%v5|}C58O3zf1%;JvfWod zZ5r6y_R)LK+W2;F`eP-m=!f?bye~{mSpD*N(?2#ZyEa6&zTap;iP_HFWQj|Z5a zs^%Q!J(#MuKZjgvwjiYi8Wl3W^lHxz!WM&z3=LBC^FAMLJ-RGLa4@a#GdAwRZ!=Qb z1x;`@PtK0rdOmHmU9w-C#jHSoQKzn_FSVN>ui5!zQ9u-u)c4}Tf4e;dS3Won>S%w6 zxHu}>(mHJVo*u|ji{6X#ryH)CX)}CZ+pwZd?($l%?uQJG$L$-m_+zIG#}fuqu8&xH zZ+TSS;FFPjQ~#thZ&Q(+Q)v@F2W3S&Naq@oAA36t_Z+h(Iee>6@%M|$qI{xr{GJUe zw;0>D&~0UlpuuhKfA@~GjQrwPffTM;f~}Nv9Gm`I;B9iDpkEuHN3l z&S{^2$uKkh?#t8FvLF5I$C#|^EWaOqZ>6(!LU_!}(m~r-xIJl}cs%h$31Yo4bIU1o zP7k5}f{oWAqn&zJ^y*qySKr-s(SJOn_RKK8vZ~9|p^xyge|3wu{SPO90T{*Ew*AAS zJ3fOu#VxqIy9G+nP0$eBU4pv=_u^%XyVJ!A1VSk@w@KlAqO(>n_GX~lIhy?W(!hCBe;!28#&?^hm46EGO5DGZ3X{Y*Yk9X zi+2`Zs&L_Me}TJ&X8p;{SP(kDW$vi0*~fL<8k{QS+TlBk{SnXkuT3%ZW$}|2uN0ok z%{k>& zQ*+H9b^cyutbVk-5 zsj%zitME2W7Oso$nRsN^x0nrcD&;?R+Q`$k_nVLEt~0ZREBkwO+g>XDg9p2pc3d4? ze|YgLf7#rZM_-Q4)nV}3Q=50)89XriaASygzU1$zCKY(pxaac)6DAZCBIiG--!yL1 zKYqnV$6s#wI7f7$@6TJU8(eT-;|5KO$nCp5d9xsGF8}yqUq44pt`@nn+N=f%{f3|Y zaI8nys0rgL_Bpre&h+`2OG)v?S0Cwlae|}nf9vOuY8`k}Gi~R;hHOq}{yp~l*s70S z8o9+GOEzWd``fp(-6DsLT^;=WMEt%FVbd-wEglhgtHkKzIbJQf5Hpv0zWQXw^j)r{ z>>XWdqxM>yy6vAVhXNCtO*_`#C>|S9qw@xHd56Ynnm*~4^<9af`A((2|M%{H2W_7e ze>iPphB2#-9145%Vg95M+ZXvC$x&ta{ZVf(>buXR+!>=~2&&jjsdpzu(}wY?MJ=dR#ZN37=CUxHFZ&) zhz0%6J!ltnJf_5l#Vs=TsMmZ_k>}6ve{I?rTCi62Y!?e$R3kQr1>J3Ux@pINFV$D3 z4k-Vs)5u%}X~)%qYc?wVcOEgFdbY<#?OeC=%zUvVPugz7ug%-=Wboc&Tkp@hS}uM1 z2M0_1n`&~(ub=X!eVS|Czf&JAIrXJ#rX20&Rt)MBTU#0vdAs}D+pF>)3Yju_e@dqP zCwmXt`Lb}+j-Mv<>wR)TVx|Lf{nnum9k2qt@rM zk9{_US|WVzSa5pOgUS6)r^?_r{nX5p!>IKoBHgEIH_1~o(<*)Nw2%uco*wEx_2#f8 zdnflUI<|RT=cMg|n0tTbxo3Z0e|@-M7qwxYcVCV(hdv(55`DJElKLaA^nA2n{pj`k zY82x~?f#zLk~(0JDD-b)x#1woNsb;4boYaKhud`LW5VGv=tyY85w;q|MtMk`Sa1Y^ zqzJ5$a8J`*#;QzeJVa~@(NSLEG!tQWI2J>=5C8tk9m4JTqrIdWjyCAYe+!y*I4YCp zT#y|1B0NV63ydQW_rm|TEQgb$07ay45 zi2cBU;m4*$gGLI801ELoEo=w&5pMO0_cY-{{^4L*9`c9QB!V5~5$vcKfVT*MH;cJF zm2ATsHn>%O*zZ3>tYwtZe_psEb4G*{Q}@<+c(VLQpuKvug6LDS77NERim>Gm8O?G` z;ye~(Vj!)6M0n}NLO~xtB<-EV&fwhG-aqmT`XyN|;h$IQDilMl+l!7iq;PauTq$0zBcTwvO;hD#RI( zG8{M(DFM?(f{zT7RF;5gb~}(#^s+f&ErN7O zx(BhNd{P~QmKI^vlGnU}(>)RsZWoGmeju_u=H|aDbR7 zY;poIUM;r@(4*y1*1L#gXCLe66M=w_1aR5@p6ri!NQ4hoM?^&UaAtTi^+x@UKu;vt z7;edYcsS}?gs;6~o}-e5CYhzQg6}88iHLAeT+)uQM9-s=1M5vRiRkeE5q2~Q_{yiK zqR=4FAMpP*2SyIJTJL5=1Ywz^xxE5zCBy6WJ^~Jh8%2|Rj}I)Zax5i?4uKarf#H8T z-2DG_e1IW2ozf}FDhXLoS=K@Gyux!1UXXvP7KxR-e|D!ax**X1fu#~_%O;0_?sakz zYl34aiPa2BF&N5WXre^_ln)2;ugHluG%AM7O8+5@Fna==`g!ZL;^ z!M0dhQgl&~1cTzh7CG;c0={B2SrcfD7g$D61r1wDqbT0sDF;ncUN@F#!@)2PgViK~ zr;>FAf6cKXOa0)+vLtDO%&RJ=u#i7hlzzpe?5|x?@JcY9tdSMXiu`}omP6rfJklKo z4T1h2ae@HBUJRN_(nmpY$hxBjQkQhahjGqUigyay;Yh)amrXY(E z{PIXZaf)O(e2QO4IuiIoq9{3J&S9To6vLr=ppVu#XaJ7Zy!fm$67_4hFhoIs2t&1K zy2|UUA}E}}dv>kyn#?GoD)W>i>$D*Ke`;A)=v9g`3`!NXA8U}+bZBi^WYqr(y(mhm zNdH0&9kQfLilGa(c`A&k+Ik~%y21+%t6p?jVp*&gU638Bs4AMKFs!V@mN;28DC&pg ziA#Ru0rBi_PS5G zA|4|OSP!wt6!6D&*84&!0DXjR1nG>2?4`21ZWKTfYy`s7qA5ZI4T}e1pX7i{LOMo6 z2#3OINXcKabaOl;W-(wqPKp3ffAkRbxGawS!!Rq1jK!Db^RE7J-i2 zBDhRKP9X2lHk$3h?!j&begiW86GnjZOoL~ez<%3-y=8{uLo*p-9gLVS=7M9(AdrHV z57E#&LWksk<75PEV7?T@M!`Uk`QBSl3d{!*Px=J5bPy2qo%jWh6riPNis6LY;YdNMYcTc| zcx(`OEL!YUkg0|!Q-GspQ?O73HK=k}oZh1Cs(2y7Xp zJ`frV)T}wA9fJ-zgOriQ;pqaA(4y0Q2_wJ9qxgEe|Lq5ef#qx04jLi zd~7p-&{v2I8$s@|S9^inkG+dc)fL-J1D!=kd-HNI?s_k92$cfcA1tUWln8NkIP|!; z%m>JX^rLKk6~Z9MfTvdhoI$ge4Ezmn3D!OX+Mx$XC#-187hF+j9Y3IhzG>dQS`YGe zDH62Efx^)Zy%4kxe_XOUND~0}J3o>QY`SsJk-4z3*TYHRPms_L(1%c8`}fAI1^`-F z5c?217y)Pn)cYx1H*6Tzja>|rfhp&V{tPBLd}TV&=C4Q#?~0%{zXyi_0EI=6b4w|> zr_6dlbtJa7-+;fyMgSUj1uBPFHNS?wITdz83<2w@#`k!1f8I+EnzFkGrls|S(r9l|BVz8uQx?Y5v7fSN=- zCt(9XFHFXYngZvBO1q(j2K62UE%X}73lI|Q1JVM=iM@NVt>HLPGAJ0B5oBxElhgp| zpyC8Iph(x3e}4{#1=QM=Z$?yyG$OsC_52AZ8XW*)2JtkobEq5C@_jf_W1Q$6)J?yx zV9HRR>9=6Tv}um!3+s6dnhr1u?Sd#nU{v5zRzZ~okp*#TS0v-xdJ25wc-< zTQWmPg-yuLt}<}!$qyV#1btNd=6F^i2b@9>@s8vOf3g~w_2dME^4ZX}*uAT<4G;y9 z1do7BxQ{dh1_ta0ZGgiK=;}-k&pij)K_~!J;M@}f>UJDf2LARMR`v~6_BsWIzQq^Z z-cV?h&zSo>gCMEZfGyBwaih?XSE_=|rd~K`&3?e&$*kni?@ zf5lvFe2!h>!~L6`ZO5D60Y|6Q`-rf2U97Jredap-S=n$J_?!$`nkZB9%{DK!Pnp( z=?=HKQRMMVm;{Wt8XD^hA`W}Nz9*tMWM4e4e>*Ja5g^&~+<;^Vf$M;5p-w*G3FQI+ zyI=*wiQqK~Mb2vw0=SkLu&o{3H6I)t1c{)HVH^iyA&ZlMpq7CzpfswB zAX|{EdN^;1wKEiiJAz{DeYC6U&;ljnQ3Hrw&BK!q*j0o@@mPHl68%ub5D*s&Q9hE! zf87HEw}4K9M%n@^AqWXE;dXpP0RTLPQL!?IA!IFTiGg?m)&=#{2MrXKJr>ooY+dx^ zJ=k~uVBZ}D8^Nf(Lcs-R+zpZhBI39SI@n4D8*HriL$(GHx>E{0{7PGBk!4stnNbnP zqi!|;#ZNa7kf9yGHu6;nR0JZDmAxVtf3reXiUTA61zHM}i55DV=pY_lJ`5qMUAQA$ zfW>1kP!nL=)jxa$fQA!I>E2a%bT}5fn}$!UST4w?)c9R@mn`BWU7JJPb2yk|uL3f2LdT zVQLvIUX*o_gOd*Hv;DTR?_uf;10>+pqaT4o(!^8ZVK1Ufk}u^h?-Z?FQV zGrG*%EkHm`S1q1|LR2K~C#q3?$ssnLQ8eSAfCU_y_MfZ-RBmuTTrwQA4BD=GSRh$g zlDnVEDGnn^;{+d#6AV#A{1ADle|R{j>5?o`ssO#qknQPoGGP0K@F#(SLtuUaHc3l5 zO(?%SO<0WTQ2sZf8p%La#2J5uTe_e~jK~W*cCaruFu(W;k|K)=|r zVF{5fFrD=`bvTh{7v&@j{+wviY6zQ z6;1~HaCpj0p?H-OHO33u7HcO*6&v&nN@F#~aG=IhqQdbukcy08NRp@%8CES5hjS%O zEB|TYKcOdN)&f*d2kRU;b~+Ralc#yk5e89n?R5CM zzvAF!%J`-G+v6l{Pa-KIEU5{cq8b{@NEmG!TP>+iCgRqQMoWyXkkWrhlP;pj^9l>S<)9rF%o?I07!>VEpbm#^U%_MzsC!Np{$Ect5-USV z$k-kX5llm%Syq>Hf7upHU1e1}Rt^S&kL_iMjP~ECtqBZ^ohm94PWJ0CI+D;>B%$|` ziUN8~Gr(7zEkPWwL1q2GWIw7j#mL~r6bF%`Sc>C$4Qa)pse&UJfRSNEL-G)YVkjbk zHSX8g%X0=zF}f%v)sSMD8F&n<6RFZETH<&IB^bPfBE+)@e=ssdtB#~n7exkxE;<;W zZcZK>gfPaZn-!jS@W1AGPB8$v)t>+{3AHqfl@*=!;Z=j@lpjSIR-iJXjJgKZ&pQhqfSo&dmC}AJ5>Q_d za3zUAeSBV0e;Ag6TniozMeV0l$~q7Tw)PVvz_lw5A=w=SXn+v}L+8nngQEopNGD9s zYL+Z|?L?AzC{D*O>|RO6nJPs?9x2hXPo4h>l=-9si~KWj0#is zQt+1SsX8NL@?^)aO;)3R#RQDQ5EU7!7dVq3s)LdU6G$Ft!2`U5(>Y1id5zaNo^mL> zW+)Cge-rYU3Xk3V!~_oe#PCyP8q6=4fS3$VbCd`1HCmMgs|iI(`PI!b_^t%dZ-^>1 zp$6?u@!0UTh@<)?vsRj%C2%MN9qNa!W^A*zVvUw6>1YBHX5Uu}iEwzWI$0!qN*rz^ zE@;;wIdKRc|FTC4DY~f-+f7`wC zu2|XjFcu<~O;{k|6gU$D;=tQ_M|{QBI|*pT!Z;1P#u8Nzec+!OCY{4|Iq>*naUly_ zXqh5uX+$J)%CH$4v-zXR!4n?sLUE*6g29AJb=}&Zg~23)8mVQ{FxaQ|SbCCmV4r$a zR#lRim$Ca)#8OEn+@xbkdQ>0be>S`{>vb|B)!x$sb{uQ5fe8>_v>H#c;fB6Z)QFTR@ zRJ%;fPxG0}DlQz=;IOG_F9#8kV8!ILA!{fS266Kr07BQXP;`a&(g4_$;QF4wUyf6y z)S!Adv%Qx$kDrlmKydiD1PAM0Iqhk)5jNQebS>x$?S*oEvvY!e@uAozRzdpj%}g# z+HgB%-Zt~kejj^TN+A}>RAc(m4_yXV4|y}hb5?z9nYUwn z&Rk!v^^Ur9WbKQ{7XyM%y?D2M;UA?AjHsRBqz&6eDR_~T=*thBvt{eED4%zS!Aq?}FZ+pHFVM{Yq%h-Sv8ZtCGq8af*Qx*1 zi|Dw7osg$e#Zs59zAnAw@QAJxXXX22V^R0!(+gg$e=QKut3?`D`Lpe6>=Gi1Q+FoL zUVpsN>^X&B)a)Gn;9$v<%U2z0QmnzK@!!hljT#bHyXvtPeY2OJ@bW|RuE!JSOp2&F zCjSt>n3VkkbL0=~p3t%M-2%PT#u=Z` zT7&-eLrzV5-1=o?kz)r(G+E@jHHk^{(v|y7f7dI)cV^f9chu@~1)FSm`1R|FGSLAS z))()euUY2o%{H&8-t+sVsylXuZQQs%OH5Ss>XEbZX85z>Iz3-Vi<-}tx+@f^8JGI)Azn`sqZbLM$;3YUad@j<5La)p2haI zJaF7EwnxtO|L9F_RP3w2JFuzB>$Dv*_U*SU&&>-DceW5lm7Q_-+rX}oAI2WLf7$Hr z+TE@fms>TUb?W*4*@ENaY-U*5SF4&XOSxs^*H@2EuRB*~!!19d)X<`hs})Naee24= zY%x8u&dpumd)kFteuU)pRO!N5M-QK=)4w^ctS=`;6 zbou$S^1jS_ziZ2LInr#c?H<_gfAp!TVL39)E4BIX%8u`I)#09||460YpQ~y7@xG70 z`*kZnI7Oc92iLayduwo|@C=onx+c68T7-RHk}dq&t1R7TEjT#s z@xPt-KHl1>+nhnJrE`~^JTh+dnnAm2536yv>h-zK0`$DV0%P~R*H#3}f5Fc#zTEj| zsi|$#b$>RfO+|HEN+p0X*SC^?-dH>zZCGWQl*wuUG-Onqpr|-03){zWf&%Z9-Y;)Z#*Xy`$ zWh$6v=8FX@x~F!x|0Wkbe;Gv`dUkVTk-UwTt&dkOr|}EH*9{simO_rKD^PDi|e?^toN%2 zG^;%(+rDD$Mzt$Zs%`E{wa>YWrK|kqX!viGJLhjLYmTTobz;lYe<}7p?(LuL&h-0v zj|5KG`&;kGt6{}|V}_+abVfe*Yyn$fNX{#N3}nic?Jic!|D|lb6@}ua=Nx_{&C0w5 zOZLihZO{0}cSi(t+gzd2Yc9+Fuor)F?YGAVxyRnfw<@;6T6SoTkoV!wi<+-KacvrO zI$vhO{?l1^bPGG*f609!^Moj2(#G?lr%UK7e%tcG9XRIbkSWI&zG~jnOw;Cj+sW-_ z-i@udD0P7xn_mV!<+_($&fZ)cUm(?%&)>f1y_{g6u(_)9@lZ} zxL1#2@-K>-*Yj(sqV@U}yH@7tie`CBXWqT%PUA}rQ=hN-f8a>T-!kwGPOMw^Y}uPf zdowQUH==&MupQs~S3Xrea(;&rS;s$ZIAVUf4WqA@IaVa55R?7D$i;J4JXy7N__Gxm zif$_OCNXX0GZ{LZjec_MXp{5jn;R$dSNXKG%g02wW3{4eIFb5(`IJ+B7Y;3-e<)^9 z=1JlA0!sExf0v{DrnxJnf$xjIU(oXSxG9ASsTs!vwmkS|>_Yj(bmQKL2I)R+8eFMh z$e+P~KhAf*drZjh4+1vRjK z<8pl2e?B=&p|;awuD#lHlG--5QrQ#tjT80P6w5nQtsUBMM6rs6sucP#d1zg_!^c0T zT(8EIe^P#8;H(X;d*uzRSmt*6lKZP%C=$3xle=*g&r2%K#R0}qq)Jt1>?cqleeWawT3`2E@oF=&X8=HykIe`cuv zrrtg%w8`k|E#ItF2?+g3G) zf69Vc)CYr7xZ>+>`@S^m)dqJ;)E?F5{@j60xm1f|4z^y|^RkmRIsCxBZ%;__F@(|(T zmEsi_r`foFQc>a9w#PRXf8P8(XW?B-%~i!307pQ$za3uDbxX}+**3}V2R#q@bH>^O zo#SS9x;?Y{fdjd(2KMcJyt#SIujsgIew{j|`JC}q|1=@J5A41Te{7Bm0&x8k!A_gh~(+v(XSDQ{>f`(a+zluwtgZQV2X;a2VT zR8N1)-Ry1kdKK<(TUq7Qxm1qz>5HFhvpTN5Ux_Q5M^<_y9h-V;&FhBu<#+X#?8sgC z%H#dq`OI5-bn9V4Ynt%FihsaAiECg*n4pUY*(N#cKnD;92meSg;ap;x*kopEgjLf- zVL~`H$qU8-VS+b=d(favFenTfo*BR|0TxXYU@^f&z%@+*izY_aG@%&ml$+>ECg$2S z0hMuN&p35m7+4O>#Vd(*c;JX<9C9MFAc_7smJy z4>zHxfUGg9RuP&=5+<~Wi6UZR!J(oefPL8Ic|laL zfK3w;lVD;zkXk`Nfm%_6O(cW_6UrU$PVIRNE#6FkbB77~2!Edup2!z@Vc9SSyaTW@ zO&B8qX_>&XiEB%M{9AgDw}WFN+?l4ug`g-PC?*bVA{m%SqUfL~lOSj&$RwCFK1pN` zc?9qf#zC(cDewB{=y&@By(*6X*fL zmB5>X2&`s8B7ZQ^4e^6T1VBoriA4o9g`vg%#LSr{+994_2>T@v$TqR1NI94$Itu;? zbOPdG#g=#_D6zK@YzH=hXYIOAFp>2VOpB`EB4IrXCV=%!6Il;M%rpTeOtO^oRmhaY zjuY(qPcTUxSud#n)WC@erpH6^qDA$%T1yU)>H(HaJbygkRjYMPYw={o2^e(p9)nH_ z8MWOc(raQMP0)4|xf&N_8Qp9vAi&j#)uxGh3Dwx6n`yNU_>&2~W1v@B_O!jrw}^_mY`c7pt{I4&sBie-qr zO4J|9&@_QR6HGWoSj!mh(KGUb4o-jq0j(le;Aml*R|4covpkrfDQz~$Oi6HIh2vL{bvpxfbjCaF~52lhDO9!y^X zG6=Ce@-;d2m|%HmeefA^2oDNa=YU0AI*+D|!ClI^|>WErpnsAP>5CrQhR~M7u2i}|4Oigsm zMEtOqj{&wg4+Cr>9Vb|}Kz0t+jen;Lz=$sK33v`MEf7PkC7S`9#Xl2>)S{x}upqUT zZW9Z&W3{d^i9Oc72dzz<2u+`?5L*>)CFXP6Bk9;laB4_E1;UA{PKDm}WWcZkY6DXO zK&J_fkl^W8q|Ky!p&`sOe|ixrw%?~Vu|Y_zkh(w^cK>dg1Xb|j=BJP_l79-c3m}N3 zESH+$T`vBM^@?X4yaXNrrfdtj#~Tw;qYIDpRtG)?{e~A!^bHf)+BCs8;H!UeXj|(* zWb9X5dlL!Q+8=Nvu|qR~;9t@mgmtZtsOomopIt=n0GxtPVB_!;;MzATU}R-59wdwy zwYHvba*ZTM6_emJiSxgn;D7z_!kn}sY~BYBDtHA76viVDBE(_`5-B2l=y~i?0tzY* zi{epG;X$6~348K*u7M9clnakg%cB|aAk~m8yq%fJIbd_cfFo^N<0J{|}k(@w^N=Pc)hn=NzaiF1OU!IeIU{(o@O=np)A6%Xi- zD*#sJfzf#ETgWw!n4eIS$8}&s!f}G<@hkxC;K##R02g8+)PVQG<0wW5L*x))#_&AA z5Z>Vj8q4!UW8tP;BYD&-o(DvR^9Fp+Bk}O)Jh%u93S30*#*iS-r{#HK5j@l+56A@A z%##-sJP*jk12*$k5r4ugJRlPkA$$#bjL5_TGQlT6CLXODoEwcB!+~Rf(Zf$ao=3>! zA=Es&H?$*kBM&DDloK9&5;wcTnwAGZf%L-_IGsUs`ITX zIzb;EHIRSXlgtZKzz=irzF{cM&?mzaGg~yfLZWFeXUsFs)~VNgMa{C z;Y>O35{wQ<3O^Dc9w{6qM7W0C^2B#h8m-5p?Fd0t06$ubwTTShpW>l8VZV?QNJ)%U z_K`f$1yokpOn)#R@&p`rth7U7P(@%laQ1*t@PK@h1;HP{f5Klldy%W5H>C-K06 z5=@*KF1NuCeqgC2J$PaOU4i)WK)3{O?c4E8z{57dv~esenXpE15B!5%>x$Q{ok2Kx z9_pHG8CJj(glL8x%VUAy{a|A7B|0PzOGUH5o59TNA%8HOJSdSJef$GShWrF6fR9ky zq$7a)pa*z_8J@@Eji0qT9x)8e7BS2!tY&f6$WYcKX<6(WAw+o9#BQ?_1vBIz-{Jr~ z;uE}w2;(<9z9VP@m+(AvHP4eaM?XJk@{mSEOARj1Q6qS(*1xW zWV+0|!GCLu;Jz?CI190Po<|HM>j9(17DFV(k9|NGc)$SiG*XTg_&6(iaGc>WW)O;I zJcgY_^(0U9$3&7MhX{NBmdGM9l4Aj$3V5;!Kfqx!HE(r>H3=#WY!hz4bdp8$xDZ@% z<}FtCz)E>#r$DfT(RX=l z5gs>yj%4C?iq(KH0%*a&;UqkKMtlO~7Sj!n!r6r95y_E-Ol);hTqZUy3NH{ScmrMp zBY#65fd#+~k=AN)3ZN?}0uufV0XUAQ{hNLU>Xb zJgA&~6>x<|O|>MM=h0l@dw>~UBCW9j2f&>0XclEJg!6g>J3e1iMJ?S3}-cK#cRZEIU=CV&5Ly};?aQg^SpCuG+b?dhixb;=cPP-vgI zi;9_cd)T-lm!D6LF5jUvS7GthOx4ah=hlCcePO8dxMAI5-Piq5u*t0+<4?`&T!8s9 zrc31a5#iaZX;pJgte20kxW&KhqPg9NJTAH}&^583yPWe}m-fY)MeW(L)zu+a&41AT z%_fx#c)DiXCD+cQ?ThUa&ZIe(YIn85Bg*ymuR}}e7qJ-@&wahPH1)9m?!Ow0JKsVd zT5o#Y+P981n@%(PhQ64YQS3o=D0QN5H`lwCefFjatX^nUQ1FzRTaHzHmG#x?`_D)1 zx>B`MBzxzhv9R^cflRLkT~n;Bwtx3lyQwc@*|js3l{=OcF28Wplgy(t4;j9E@#ng& zrdB`jl%F_A$d|qim8;d0HYEo%oYLq~?@5V5tD9r~Nt_y*;nUOXTx9JP-{Zn}Y_FAd z^Yq43LLNnZsx$ex^J~o9g1dX|q6%HjS*z-pQ^QN_%R2GH>HTFQ+D?%k)qe{fU1Q%r zD?44E+^=m&k9OI2`A1JZwqsTQW%KuKG=s}T9{zs)(D;H4=OnC~8ei`Bu2gWjZ>JwV zoclhaU|uO=Jn_2nb#e@Hfj9jMgyAmt8uvMuJrWq`BO@-|GRFMRaSm1=GKH1m%G-}RzXrDHc<<$TtV`#6hVQlXnWjeij3uJ%03X8B|7 z>FvMw&a{XBEB%ay6|WQt+<>yS;3D))Cw8EYB8{SR#G5BWd<6 zZn=?bRWQf(twVznZ~wFVpBl%ies!!GS|jhk%tcR4=zFg~wMK`lH~zjV@9?E<2I!c4o3D6o6kSF&glI2zMHjU8W-TlIE{$-l-jj0E z@YOf=cG_90W90%RCLa#JGjqQFWE&TDz1-a=m(>21&q~!=rhkn8v`$%aChOV2W6^ah zw!fTj(jW4gCeO}>waV}|F!P)4>f5R1Hw+ER+oN9dH8~Gf?D6(Ui|K5hvWvE5Sw5`S zyLZ8NR|FJ$o9FV%Eyb3XyF~Sm3KmbSeA0E|x(%~$sM#xI?%k{FoDF4>+G2g2$dzrpm_^jSjSN}V*mi)cvzkklW6KfBs+P?m=1Z8oPCkd?! zy@~BqxTb3E>o7-a{rJcj=E~%fgX7PqjToM(Thj%LgLj{J+;7^XP5r;rmRixrE4Au0 z?oL4KHNnQI{NGRivHjZAJ4+_c=_5VcowE0uV}pzKEWB&x>2*tI^wD3`ORU`eW%(nM zM{gOD|9`<`|B5^E_Q-Ro|E0?*ho{aq{9&eUJs#YwoHPDwn#o%RZg%w=e!k4^Ruenc zu3YC_-lr>SWq+0(Nw`O)7 zxfa~1kt6o%zpkAPug(ZqU0dtRb|TNdCNRe?$ zr+@V+O81YilxfYTzdP-?cc$+6?}anZIXizL{X`xzt@fENPwu77zC5sbcJ^(yPLP?wN|8{ai#(&3!ScijZaVMECJdm1{n7r~UTrb=c*~ zWBi5$F6>_5b8zZ=7s3uKsX3zjm6#PX4}YzSoLy~l;JY`~N@veHvQz#OcVFb4*v+{8 zI4bPVautV8ob-6ao0fytrk+tI<=gT(tJ2$6n(c0{jUF_1=9y;IN;W*1(6x(H^HsgF z=UX@1P`%LL&Uw-o8Q<$w?hhlk{u}t$lbkL8*e1t5j^T`W3%Kx8-MwE z$HR&lH%>1szN}X@F2~*1HAZKP35lJ+r09R`OwKHUd#nF7=H9j^?jzC1Zugz_Pr0F0 zKhDb8CtLH^(VbQwDpoc==++tLPH@2!!n@52C+_)E6GpTw!(<-PAn(#zPJUP0!ng0N z85rEP>AlB)eQdahs?jRA`TjNSr+@HW%QUXhluD&$+?CL2@4kY^3Qbs2ciGcyQ9VW+ ztavk1F)lHFTF8M`gIAo2%KXt!>Rr2e{~29l-+u^Ad+Gh?z4t4$);qm!U%J~3g=u-T z%#lWcwPK3)?e}g;SWG$X+R{#rp%Jx}&VBE#Xk2H`lhUc$_Al8fKGmh!JAX%KI@4o* z+fk`zeL6F_)b}C}XFG?+j6OSOb=vLazm^?2=fki?)1FKZ-g0lD`g8+5B6YfuVZ+Mo zC^>n>iIY#G^F1n)dSJb*{carS|LuB_Kh)?MmzLzd9=mqHxcDhe4{rF@>|JE(W`X_# z<_w@86n%ZS#XZ-|8GBBDn15a9Qjy|Pesv2sJi6uks)viL-TYB` z-rv2x`&_CD8B4DZnXx^n*si0mOOzgXW)pSm&+#)Vf4Px2ID6;6-<`ZSd;ii*pLf)7 zS8a7JO|?d)a%a7jP=9azl!ZP;z2CNX#I|5}(FNPPd)#(r zTleP8%k`DsRY;ueczHMb?zC&>H|)3i_U2>5DqmT@Hu~A?_DkO6pZ=v>4f93zBOi-g z&L0vrA$HpF109~{TYuMa;BvlriNjf|6O zkM}g^gNL@N*|vR`5@{9p1H2>YizR+nxbgO~2~H z(hiyMcks6-*KUpX&&>2#Q6zt%tB*5Iocd3WqF;ma-D@4$et&ht@P@DIWhnjhO_6pp zS59rMQjX}{tfpR)gcrt@}qv3}jOLK&hyU8|O}8cZ#kwgd%14b#Gj1UdBkj?WbLuFXwbnN2Q%lWr=6cQuj8}~yRMjb8vN5Qq4AA`w3m*(-Z*;ml|MH`2W`Dw zVRze6-P6s!a%gVG5|Q!g&t1)4ZuPE$$Dd_Cxqt9z_I8Ok^X1H4$c);U^I_c7r4yVN zt7mLKeQ2-RfBbbW@_5b*Usg72GA?t;Dx-$&7~cDfx~SgpRdbxvW{xg5x9QRxMbF;9 zohSeEUhSog8#cDfQsP+rv@Hfc^pmmhZSk{{9UR;2iGcv zy6ta#s#yDLf&bLGy|U!uh{0nAtu6XY%zx7>Gu`=I%{{d*ADtBap~J!sO(u+MBF@P( zq2rWvug0ugDY3a6&9z}j9PWN#n?MD zOFmt3>A{86C0lkGmpOc8^u08Lm-0D_#f7D-ajj;~p(9W2dl7bK!nb@mHZ*p(DSsM0 z^UcL`zxTLQyZEJ^1=4H>6?}a1d35VR8C_+oT<#;x-VwY0wDi7k3L{&lH_oti$ow0-Bz9@(2` z=o>$%QkQYT2eh8`p8hfYbN|tG(|@(eRH(=29c2rfLv((3=#E9>wlDbl_sQF{6H}(R z_G!}HDUVteT~l&z71tSC~PrU(qDiVj|M=mKhvqO-IE>Du6E9S$;_gEJ_> z5Li($1crr~L_y^#6{e;r4u1(KB?^R$IE51_jgeKw!807ED>UA!afYaiyl8ML+`!@A z`5*@w-l0hj3UQQ}luDC2(0_E9(FE0SNI()$2ZrR(LZK5l!OYQBlx2hLU9fDV-B}h7xJ~%~G7ep^if67(@h?;uMY4uBgd6#8p)xh=#5q z%<53PG%fNFI&bY#4vx_|2O|iE>;s&T9YtbQ{6#JtCCU^_a~dRyl7DHD;tkG_beV;R zc$LEcO^f_)LBpX?2>Up*L1W?!O;RXc(kM;kX^By={Z&e46b*KXM-q!FRF!5NIwepd zPtyv`J7kU)C`Ffa-k};2&uTEZ%#cHbgVT9g;5A4!ScTvb_yNktFNXdM0m4xadvK`W9@5Rle1oncfW1&SuIG6i2r zw8L;vye)F7gO{KHB+3BGfy6OT=bB50M2@<8qB2j#>9DkkCb%O3Z1LqydK`S~f z(KN?M4992;Cy0WCT_u6#=(Iy&Bpo8>V0Bf21ToMTlmuCnHA&>5%M^u@HH2@0W@s52 z3L1#e1S{zR6uzn{8l%e&MbQMA69rC?U@T3A-jsP>rWJ)&WD17VbxDv(wecD(+`&P& z@Dx-JxyER!CV%KGxu!x3v7AE7(1^MqD|oJvXq{6TgN5scAgPilK^X8$jy4n>`UUc> z3yQ&twBdj$bXwsUMxrEz*r}*X4p9lS8oI>5bq*TbAu1Xz2sD_aCJDN#!9A=DwSPymx>Z0&U2&B(R(eoyN2HJ1n7| zcm@B`o+Lw)Y6h&I79kf7Ntdj1jZSl%Ow5$ic*7ykl0$Jw8fb%z@E}TpVJI|2muDqS zfkHAgo_~W1fxc8A>kiU$lEe!%s0GwEiWX5}N`&&~bccbW1^2<$@%LIwC{(N=DsZ!a zzsf*`+Qh%4MQPAf3RHsu;pZUmP)e{CSz{z;=!MKuG^K$|i5#t|D2|3Mw?G=GtSkc*Tgy2ygq0B$It4Szb# zD>@Vh$d@mKBi+#~I6h2gppio7LqNbnpio&ug@J%FbOA(0WAGP7iv~c9zw{AWV} z89&ZReh@K@v%YpZtAC!4o*T$pOHAOB&qh59!|9X5l>Jn^(|2?Ki8TU~quyL56;gaszd@&xe@-$*xoaYAb%a4fk%R$Km#rp&*pa*yCC#=5uAsqJDqzdD_E$#Wz`+rPOoX>iGXiQ(bus#R* zN)V9+k1$)0`{<5$7rx8)(R%!F>jybZ-mumTL7thc)cBNHoX<=_p2vJw5O232es1SX zGK&8-r0;5zFFV=$Anz^qzP()tVuzJV;1};g(sFvO0KL-b@mTnmMFX*#&GK-($?g`c zF>-(ivc)704u23GnQN1aEc1xP}jXMwm_%dLp2qo|`>IdM5S92g}DJQ60MZaY+PPOvlp zk&)#+c74po)4oG#u8e^OpN zWI@hQfI)xZB1j_Z#up3~f`7tAw;S^(3-}Sngr8v4fIe>P1#^%;L%??IOHOi^{SMd@ zz9j#8Uw@E55L3{2H;^_CXg>*14QCAU7TUoLtMz#(B zNzxTy0XJBRgG_}B7JWvAody9Ckhg|tw;Nvcy$6PgOWIfv{)D26BfaegL`Z(C?Ed)$ zPl8-SJPooBh+{~cy=*x4_=YVhc)suPX7~hE>VI~FrTYd2{slURTd96Xlw6fvM4*9Zomxqz}EUXu=a>=5dzAx`Z=vSW`u!DUbvTMNS zllQIXokrFIPl5}>N|Ud`S|tdA{nnbp%BW{B04{Q;<#y1Std%Y|;IB7Am}HUmEHuup zJm6NlO?(4u&1~()ZDj@ChmdbT)ZL)gNq@InPXh^)slcV&a1gKug8sE`W35er-=z|J%$|s_yQ~gZlJ7@lYdKL z22w|U!NDKQqr`f8xr=~nxb-@Qf4;P4_D}X?RI{*-jcKS#T zJY@Ad-s}#w*8$uI2Y?WAQ-JLe1aNUyMspFJyK5~Y&K}JMem`=4cG@*-O`?&#fOA`Y z=7L=ilE4p060ovR06v%3X+nUi{7CJ%tl0sX;AbpZxr(k6YLzYMOj}TB5PudshVY-C zH81#kEGa`JeZhL*6v7p%k?ACN{x1K})wkR8-z`ERa zbUb|rmmy35v(_8|2*Q5kMtgEdiI9;z?6d;}|N7xNL*XsrGk)PBnQ$Q|(uk!&*46-h zYax(@gxVVH0vL0pwKNVQV}E7Og%J-*B+-h62}1$Zd^FaD847UXtZu9Ot9v($5g<#2 zbkN>PbJ+?JXT=95$N%C&Y>9S~(Olrn0p8}qDP2y_un5!e0NJ!N4(APp>J5PEh2r%C zQ@0)p4fRY&+8=fX(F;I`Cyn4|h1p3+3M1&U+!;oMfh8jD0ZIA|e}7<#1K{ieVGgyj zjVp0M|N2>XwI$|En#$%JC-#*Kvgjtr1aqEHN%3!o(MW@Wl~@^JWjxKetRBZz0Sbp& z2Q;ke0DBcqXIe|@ND2b%Si{YscEP&)1Ij|nyTE0U;X_G`T~H7@W;6uN)jqFF-ZHad zMS3Ju#E8LHRvozT+<$@rg0(2tmh4nGo$e$rj&<6pcDrdOCd3{Yl*HwR@^zu|TDg#| z;Jd+d2sOur!bC2ZB+fbqxUoqjnAmNV^es<4Ng+62Tc_2ntlJhj=Upp*@Pc}FTZZAX zK&h-dnH8YY(wJsvTv?y?jw4HV!9uVGTrNvG$?hRE0iLHr5Pw{(Vf;wP1URkWxeTl3 zQ1;`9>cZT9p(q=8&S4Zk44JhKTfE({G&sRv{>dis+W>Gvm$hI}C9h9oP7s*L<9Ir; zuey+Sp{St-;RDJ)fG!9lfVRutnHzdZ72HYfY9%a$khBX(9x5}`D$P)<_x!xHhhu~W zh@RjfP1~MfQ-2?xByhi+Z0Fi=T59G;4z5K0otE1JLRtHQxcPyJU?qVFhbBQM2}_XM zx-ldauZ0j%r9u&#BjaH`kRKdVMp1NnuoQaQZ6(g0(25gxpuZBOQMZ-0kwzHRS-j3FlTVC(`f3Zl$| zF}ZAbr#zbN#6`Lci(o^+4g*}F)@f40nN`?xP@>&hU;v?3yTXAeDAAK8CmGQbZzrTa z)D51CDaB9);FkT+d_sMeitlLbHn$}sE*BkOiHaMH!d^9ntq!BRP!FsC2G~v$i~I9q z>_UU1H-C$p4Ru=odzu@Pj3@vT8}@9rYr$Y-I$Q_fv#py}2jahA%`_5=%Vk9<1alP- zM;a6Fv8IE#;8ik8DIzVR3keUfz!QB}(z(TD1q*f91xiG$K`oV(C+rJoKICr~gWehE z@|=F$p3wSPNbSZ}0kd*#cN0~|VuAC6i&QUc4Sy7ywjZ(OmB=P41%!+Cvr>3XF`c2fBmB}ql4~)S+YNRdi{$xahYK2-IBy7cABKY?6T$bfxWrIPvj7YIFx;UovJL_> zIJmW2*^kgFv_ufLg~d+rqzhXhgzON8u&n4T!A$rE4*&>>L~?>$mRQ2DAU6S_2tnXf zScs;}F3KQpzhH?&R&L=^U=%;#5~SKtw|^f^_J`j<=b^uV_+!DySnDn#yH3<@QqEA> zkeL9?8#$Bs5r%eQt3Zgc8c^f_F`=L_Ef}=60dR4$4RQgoB%L^bBNSZ}OA?A0BNmE` z?t&;#DBn<>I9n(U^?@awL}9TOua$Lq5CWPo1nmzL*U#zze}8{~9)I}K-%2W6fPZ&T zS+R-!Fc*Hbdd%NHF_iq=;txGS?!s>q{S#s9iB^RqA}T>v?C}B;De|Wb3rtLmCHHUf zPXslGKj6MC@Ni-v%oT?c>2`5_MzXczMN+JIncO2Y~dSUwc zlN6Hq;R+k*599iih2nr)wm9v95`X<6Oo@s9_!(vw_iOVm7kk zpjQ(VaV`5CR$;ZXl*$SMn;;|#^#I5S zA95eCh}LYIV$kw542Ev>1I7*zCTMFg0Nx>(aN=;B5X`8X53S+z7z32&JN%?!^2Rv@ z?#Z~%=Bglp``#*P5aqIbi$G%@bkbbwpH z&fc~dGK@}>pH+X1IMh2Z=2_f2F^B^gaTs+r41>;nDx(0#-7xEV8X4N>fA+;55alLc z%=1|lP@LK2O`Ih@gMST;k?x>>`w`@U?U=j-w|58In~cl}DLOJBI(tREcnOlc#vrRu zN$?GbMoc)43)&oTw;+SbXh>pshdPKJ3KS3{jjMd1HV!34?n263Tp`pW<=_m|NSXoY zRc0jkhA($8tzl$rDE$B`FjR9qhsZ+cf^uv#k*rR09vBC;z~u!p5ELM>RA9m7Q!*JN zq3b{!(0$NgG2#wN5GTbn>J}N-hkENng-r7qa5R^H973SP*@)E^-NlnsD)4OvA!I?Y z5>Fs~aZ{g4#eX)-r08Fx)D-kTXX0X6qaa^Xhz-(3Q%{_@BKLsYhL%tj37#XH6gq~N z*29E~4d?t2Z3Kv%d#wHfP3ZV;~*hc(@uCDPx;`kOCl-P@haZXZZK=F6! z5%Aohyn#eg+>s)~p{7ouTM$%i8bXWg#npmulN1Py?|-0p;?e{MAhv}NEFKwJmrk=u z3P_2Pv|=$87`cj)vV!^zqz$GVh#qVu9)iN=lG z!w4ZL!*}SXmg)_>;N}M@1jARM8`Htlv?PnI4FpROXW5h1{mKZ0jXN(ys*sTbu^~Xl zR5)UX5`U6wqWG&}#F1#Wiu9|{I*N{3vBJ2aR1jEh#M267QUz5ksx}SPY=~jVqfVsz{0IzJE6L>rKZ#41T(2MC%nzTuMJ$xQY^G z=tG=&l}|qAeSG4(UyUYtO=7nS?)>S4jj#1g*moN)?)?@F__3a(d z66TZ#Y1U)!*tKrWzl|D?{PE!r@5+22=|3j7edDG+l>W`VpWU3xetcGL)q``t0?;oY z$$#Bv=nuh+t^&CHNJ7tiwvc5@-7_*NmGr^5n$R9Y@}FQ;(zj zs?qPhu?&m+W~c-^e7ezpwCbw6n1X*F%JU!J{!aHzoU^lTo$=hQ>r=5W#w@&Y$;873 ze^q-B-ZW&^^fL+NO(V9jhDS*@{oZ}&Vt+*X_Q}RhL!?vC2>H7%pS;flTaHX%MEw=_ zqK(~u^BF8suI_eXH!R10-h$fJT)K5$R5{h1_36TcQIXwxG4}VT6Gn~NxntIheUJZe z_Ue^ep}!sn&sn|W=oLHmd-vdh_s#5yI=&x)O8I@?Jm>N2PHJ6>^LPR0dIo010e?R@ zYQSk-)g;8tXc)_k9{bv$iQ`r#&+C89<%bT&I?-!X->&;DJ_yz75ihLlarDiXo-psA zC2y?w{fNWnr0#yBr9aC(gxvVpXWKR;R~~srx66mm$xMIlt!1lz8;>DSUVoe9LRU)W z756^8<<5p_LpG)$rKWw*ItM3=#eePJmpsso;P>c&lhz*e5Kj2^%r=7G5U}llExoS& zX!Pb@NEW?uc`zL7N`CZhJI6fw&DzhQ{x)o!Ffef=!t{%Gm5j7{#+ft6H9wL#RW>&{s);`KS+n+@@E8uPQS0fF>MUDZXvdZRYYsqfkJ;SZX;pMRfz^7~KU zGHK>=Fk3tc9CXk1J5QwqI%0!B>x39DL>}AP6BGXJbQUGhyu%?U+m`Kn6gZaF7TZ=l z|J7~p4anbh%$--Cy?n_N4SyUM!+!QXcln?`ewX;V{&Q=`9O~8b{v@;A=MWM}0|W#;j_*Ni=L zRq+C%??vr{Q{Ob(GPCZYmAU=?x^Z2P=Mc)z*L*5SL<+yUO2*{w@1KW@UE?|(TqS$q2a z8><%XdI2Uz>ED0*6iWYpu#)zQdF7*gl#lXJKK?Hrh4N88%6~`sC?DnHf5$;}OSWj` zl=9(}A67I)+f4XP(Z-y@Wf8Wrn{cwX3b*1LH{^#Kyd#AdOhGRDkz#50@zT;gZKtsj zysKBT1IVXon+-qO4KryH*iA||knG6X4K$vL{Zz09sn96e8FkIwzP8=CvwLtnJ2&To z+Zk2-O(tM?qkmQLPCvan%lIjVlE*(zKUnjffxdM!UT{<49Y5dMPovp}&gF2@xiZqP zZzm}&WLh%SK!v?1yD7;Vtp)3J?u7=mUkOJM6oo_b4l0jKeJJ;!%Nm@vod}S`6gERS z=Y*aKJ#HUe8Dp)c&g^C=hj1;SRrXH}Z@J_4sUepKG=I3nnx`nc${ZJZ0wRI3HY+<( z2@T#8AY7Hh7>ysO(7E9=A+|sTJT)4HTu-+bIO?hp?VVn@-*ou=4|mt?Lp&D~8+34d zTG0kHJ<(Qfr`hmhN;Z^-<|ZAH0J|ObfFtiZ<(~OVApTO};{m1YT*)3Acyee2f6hVN z(D2h7v421C-IjOQq!41s^=$n=+^*zyXWH&YD{1vJrtA*Kl=_ZgcaP! z4?UfBnPtK!BLSr-($T!gAhww=6lyo=MYGR@lz}lx_?CBvu^4!2)U3i&@fS8Q-By<+?Ps+ z(c~Add00s2 z(SIe}O+XJX0|q$O6{Z}UIFVFxJ^A7w8e9Squ*Zy&M@RMvn=+B;r+t3*WGuT-9@!v~ z?}-LoZIoe6AuQQ0tWp%68;LRl-;ZM2eq@Z$gFGnJG#H;IOKbs9_|z}VKNI zNWig7!gqHdpN6AJ;A{Y}qYg5$gOSH|fqxC>%C4kyr8v=)QW6nNF;-529|Fco3-~mK z{n252FqEZ%eM%3;r^B4#sE5x(mDCxy)IDuVvf=_<1>SnwLfznh_~iclf3bN)CC7=AR|VZwgJT1sI2o))8g_LWfutH-cpMt`cP zL#PuP?jgTWTPQ#Z__UcP71D~UhFnM7Z~DL>V}ypB91M(#0$0G!mCNr)8+R6tVw6CO zMS9QDXZ%#mX8BTa?*%{-u2{?#i_$>}{)_lpEJi2_ZnbPvlp{J>sakS{*d|N8vnBWm zN(Q#!yXg!K&Ne%>4kxmFo?hKVvw!ZCycV&aER7`tKtq_A7=U%PShP})e&C+0%%CI) zbca};4KsyCYAiZW7fND+{>tvQiXrF=xX9qF*viB62lgNF7`7~y<_r2GtaRcZOH#hi z(=6gV#IbN#CMh=t)a^!sqp{Ivtc4RUvT|YX3nvkJXq-t!no3X%Q67R^kc32BWX0v+4BT6k zN+p*0?lPN`m3UhyDf~oIEPsNwVnid#jnOJo6sZ->rDDnCC`|=$RNUy4vEXealCjNg zj_;=A(p5HNnEq{v3e;7SO%khbew@xnS*(-1}uxg{aOw%|vEn572z&stCxA_c{T zC{;~(k0pG&$f-~GQc!$ZxP&X0j1+Dz8A<4(!RZuy!fvXl0;MXd;D7!GiF=Z=9lz9r z+4UJ5B$nhfW2Q#y>d@?1RJ&4YV8H+bDlI!I5QC-cK#pyohF3v2*SeD?;^GjuAf~KXYMil&t z>fNh(p-=-q6;X{@m49FX28}6>ID-tc3hQml9Qfxx!FLHTsJ4nFJjKp&w$+bP=1di$ zri#vHqz@|hJep$wpEgo_znY~TdM^%PtE$D}tn`o5tSJ<6j8v6KZX;`nS}NBBc?FZG zsf63($YFMgXIUM)&qRw=eLYXE4&N$Of%gU|H_?Ly@^z$&gMUql>RN1FSIISk0H4MB zR#9!M8|7n7on7mrot4mUGNeK&bCkq2&STY5!SRpOiIPQ+#gg0vf6YMf%Mfj#3Q515 z#uc(QRl!Oy%}YIpo9d!e0f8+8E2?a5pfPJRad2f-ZGAj$kyDeZp=xJ^U@=>7h-Hf= zMkJfU&$Sr9RDY4xRUMidFTJn2rlcBGB^l>7W#C%^GoF$yR>7sFvLZvdTWQI(5}}_? zWTZAl2$d3{l31nMxHTy8OEsl0UBL9UyFPW{(g*LaaFvTpEHDX>%qGgM1@qxHPFv_9 zVWl1$QLrgg574pO7!sL!kpYZP3TCPXW09(e$7D&80DmURK!`KUbx8tKO+ZOY8Yz>E zRg$SSPRvyxIBGc8z+s$g7*BLlrEG23L;-q9v$mlyCmWI&vy>=^s#?4UVg{FMs$yJ& z6G@z#U^Y`#jibl>h}WvzT0@4Wf?3Jh!HE;JrZJRH6zT@FIF0F^!j$#nX-Ye+UKyK5 z4V5fcGJjCXuvLn3oD-!Mc{oj~IN5-DC(dW#h<6fu_?O%;Q{zM-!XTv!lrTN@1r}$A~46-YW?a=*Wn;!Zalx;(oA{ z!7xSZv&j055b8@%7B9Cl z*@>4m(~`$z=TTK~>7e0o#7mI#L`rxX>x!r)7IQYqMXYM353V;>@{m#z_fb>|R@;%u zbbndPW$k&M7C1%AvQ%Y{I6_C=&I(7f!U4t-w`(n$L`SKRs~IdO?Y1~&V3BJj#)R-> zU##<4CR%-MD^`72vFs=w2V7fEdcI^(BeI(IsGA~s*Jxd0o)*3Dkpv_6c+k8PYvY8q zR6a;whB2`_!dSCfwoE7x3RA>o2q1)NxPKJ~IP-$G@zPL*bhLEpB7wA2Mrz4e4N6L* zAHohL*TG|2+I5n(aSou?#J%2L3Kwnqt9)j}~`{6bCOJP0pvt0uE4 zwKS1_08&7$zhsi4r8}v+5@smTfI3?ID_F_^uq2e4N`=5)Yr?e-@#T3)VuCM}%#`)f zF|>bpSu=Tr+ySVeiUTRSC-T^<7T!AtYk{6-y<_F9=QYF4x|w*^5tr|`dST_2I;utI zXzev?k93aaiyFPb3bq;CVA%_DoM0~-6t1*-1VSsnkn#zo2x?DMbp&AO9!syfWTKws z53NJC_Ly0yAcm+W3a7B^Lg$5sj_9mruE&3rDCA?J8C?=@->Pv(ecEY1?EwU6jT>*) ztt1ZZia>Krn0X_M61#r@vJ}VAB4D)@wwl*IWT=kB8(eMtW||d3*col5E01Xm%X8>- z5Bh)ia1axeh9hJ|U@;F-u0=WNNS#x4ushH2#OW)CH&)+NFN^}J@4A&IN8FPJe=f~3W6 zX)IM4Rka3+(H>?TXro2x zXtbP4Mp&FMTt0Fie5e$0!c^;Vaq_&*A@p3~+Kmn{gmskM*+>ZYR7#Ln!$p4uOF#4u z(oHjxNwY>MXh~ZbK&mHP*-fmIQ^+UCPMQy23v)>6(Qct3j%-Fn(TXmC?ssDxjFM< zEUb{)m7`AU9&|Y+GkLZ-3S58m8vE2%Ilc^|XhATQD^q(RR!Fe?Dp935!0Nz33OSul!Tv zcNO&y+%x6U3$CvmbmxxAPd)r{BzBpjm%jO8!-D;OeB_>-|LpiPcOEr){k0F&{Ey=P z6T58P|JywCx7A;td(nT=g|m*B@QXus{&#rr?n5qn^5)yuOke%#sxBY(IQjV?+GCeH zwp^FHZNq@h8vHv*@a6il<=5|j?|oa3zo+FlPfWbPCe_dD7XH&~Q~&(Z%;~KUzdrhw zp0D=5W!vp3Zz~D|f899qtl5JG*MDD0)|nCyu?!v3Qu>i~^ShpX-uiBzop<+P!!IqooI2*! zw+=jb!azXDW9PrP=+>J$YosNce_K0z@#EM3>w5F{+Ly2V-GyHquxZBoGM_(uHNIi^ z5rw6$n7#k?u|Iz^SGCW7=e%X{8{Qi6S8uL}uU&c6ch|&&%18MqALXNbl#lXJKK@IN zRpq06l#lXJKFY`c>!UrF%eA-j|9m?gc%r?ZoNI64xAr`o&gJbbak#)OTHrlTt?610 z8{mapj(Ua@@D``ja6`U@?rq6&pV(6zTjucJ`EYBV$BlpU4fe>{*IeF?gg3V2^SQ_k zIol}DmveExp0gd~TiSDNd8q$6I+2g_oZH%SEi%u1I2w;7Q)ti8C9+;a7pR=PK zXWyQ8bD*B$)=-QD-ALBuT3X~5J8SkdyB4i>@uzWfE$tCA2!d#%grD)(hLMQG657aj zGAka&Pdw9>`%m`WJRqjG{U5KHrlw|^WXrxQj!=Ihr5XEPMuf?hin5dtSyItkqC!$B zp*oh3r7{eXEyhmLCQ*nKA!IFluj{_gg!l7&zQ6Auzdt`cZ`GW0?)$!$*Y&zy_kDU& zH2>Q{(wMTtE5(9Gu%1sK2g%EC008+9lAqrY5lkfque%WvQ(jl(iNh}vIaxB^<}?V(Bii%n!ZSb#iOX96-Vc7 zh*3Ur&L22P$^XOwpN3kdi3aa?AoG8Lu^UxMcbPznKWWfn3>LP|^;6L&k{DfIjSVn3nWm&w0k%o&nn!%a!iCHW+ zk&Izc3ov+FDa9Xj9#*X+l8cq2eFVi)?p6^zq{bClC4H1t!as(AyEqA6ggH}`Fhs4C zf)#SOt7OzlVh&8qN^vEIrD=a=&?#2UDAj16QVAE)XC?wb`;;6uh({0{ zmC%cV@+AJquA zF}_L`zLS|CyR`C1P>@F97g-8?fHkEgz2F}sEnJhebMy{mGV!5z*i6ywf zFu^je3`24$r+Fw+swscgab*C3nx%kMDhZyY3|6*OsZ^^;@TC|FB_m~|6nAO_0QfGK z%E5W+48|)T<|o#JkK`vzj9%qqJf#dvb0$@CFk*OEdn{&{3}T5aR1!3RpAZol$yf?U zrDT+x#hJiP1U9Dv;i8ln=1R~>DvLrFW`${#Kqgp+ct?#nB}soJ(o)J2I)VVOMHI8J zoEMFRQVEPhvVs?Zqex1XDMU)8urzD9D7}urLbsJNI*P&;K`n)9gezS8z{#kUNPCpG zNU&HnU>ScF!NgL4jy#`Y79c6(;q&>h0A>{&U@6Arm^d{-4LZbLO-}jv#+FvxGI2MKG(9(j*BKiv}P}s0S>z5jB~HArV%xntD?rzPSd6)smk= zND?jM8Wf^TiF^aj(5$8?$y9`QMi;(BM)ms z)W}#ba-DdtB=94Y$Vh3_NQ#O7Q1lqzA_`hLU>>bjN;!WHMN}4H;24Tp5-`GMYzQq> zxTWS;4kN&j?FzYp&H*T(nh3RGB$UKuwG?59Qv)W6Wt5BXOUcT?$0CXsGLKrRrpBu& z&N!zC;z=GDmO3N##V#-75gLaWgDX<096Ks{%FxOnzF>-r$VaL_7-=2yA~Cawr2)bw zhGim^5>kJbLIfB!x8Wf6l3cptTu`di2PK)EOB1W)iW19!Wqn!X6r2U9fVKpPf|ODq zCYMwjz=)OsORthtU}caZf-+!`axkG%LY7j3Y!W59JO-U0TFnwNVyMT|J`z2IhrEic zi&%guh*oQm3&a7Y3cw_TEMq(f*3|i=tr4UVP|JTvK-Ee)akU5xBE5%jw35=F(ojth zUZ*5>sX1Pxz>&ME)fzOCzNE1f@TR0O!*0xSG$tieuu_U!+H>IpSq}yh9IIJ1Gy?Py zRSiL;ltd9i#-r^M2~k>w!D5i7@)=)TS54>_XDX7qQkY07c!`W5wW_8Z%`q2rafU-9 zLAHOCvNi!YOTK|2G_i8p5r}WFB!M}ZktCTeu2f1jXs*K8P(LZ+rI>P3kvK^aM||j< zoSIO?5M3d|M5rmzVj0s`TBIapOM``^pe9q3+J^C7q@t;Da1|?=B~QS5RM%l!!LFLZ zQr(L&A=-$gX)M44b2V11DOiAsMJy(GoYP=QGaPf9Qcp;AL>$yqv_v_Q@O zSSo*Z&{jwT!XWEXJ|tiT5U6RQ&@9M@ls<<7kP;CYC9Pu64w@)Bu#Tlc!Y^or*d-aR zO~{DzUI=LH@dQ1}6xxO9q-X$IucW+1Iu-(zGL4c}7hyhT3iujUKj-XwFfQF%nR1#$aD-l_%M*8#B5H%eS&q+~xQTf3FC$qG@vM7#N znjSfFkvfHo5JP#9s9GtiTFeF(DAgkf7g$`Rl=rt#OBz-oKr0l{er*-sx~B&JUM@zjTH^STOrt4sAnvq(ViLxg{K#XMYh zLa*md-y~=|g3UuOl@(ML>>D!CPrK>smrCq6OqazV9tV2&@~f}~SDgS;X@Hv{OWF$* z^C2w3<4e4UeiXJnIMp3y4xJ4_OCAO`$}hkw4!0?8nY9LhISXJa|GlLTyJBRy2=xt^?|RwKfnMznDJ&aI@RXi#W1qen%+E+lO0-|_F zzecO=(yqn^j{%u_Tp0`GcxQhV;(?(0{G8?=24h-FL#BIwi8Z)RS?k-ELa2(yBGTs zZO{l^A`g&l&yXGf(NYr4O{h~TSE?abobY96zfn^2iF$vKk}eCMT^UM8FeAhfoCnik zW)`Rise@rg5S6Yb8W05u?(P6iR7;|C0C?-9YSnU7;=J~NDMGT zUkXKw{fgQ(kHi9=h1$gArpVOL7{DmVzOsm%L=0sBemFjqHc6a59e4Vu`QRhft-uFR zTWVHA-cWyHP~>=M14C$&P~%=EzX%G2g@VV{iISB`;h2}I%@p*UXqgl#5jqHgo2W45 z=sLiRxUH0~BOQQ+Y6h}_pcb+6tz5jJLoiZ6D9NZAcgTQUNFX8CT2UIx!Bg3ws@+48VUj*ebxh5F2{B1TGPUik+EsLN(ff z=pngBRRYb6hmnMaf(zna0J6{Eb=bRMTfqbZ+ox#GF{o+;s#6l2XZot;EO!ion=04} zEQQ`BB^EF&ppSGwL^xB2%0@~x0Cp*$3qT-C(!t0$e8BpMWwIG1apRy1GfS~Dag$D- zEFyoxbk7=4FklK$IN8<0D(DyQD0DUK)S#4MdO4?cQ&{LJujLg1k%q5~Ls z-1?CI?MrEfK{7xk2zUlEAl6B7bqJ1B$u)Ma;J`)PaA7H8CbloS_idVttrkRApWS`Z8EiUrn2ctfoNwi(o4a^)GK zkpP`65HlJtrTZ@>r2%NuSPm;M>N=lT4u7kKI!+^X&P9WytH|T#C z0=NpCk1`E9hP#sP5E!`}2oQxU!vf0_0SA2*_i<2J;2X*(CE2CHBMd}BgFh1s1jfDY>kJ^^?Msds@W{H|5n-!%J+GiC*#j%=E(gRNT z9iUBh7_2R=#zKXLrY2D$7{$evU?zW{5!`0I39MZye=yWeWO+GSKaXs?tEBWGKnWcd z(v9Z?s2SBb9U=}KU_sLxVQqz%|j2@Mi-kP^>mux?NovPd^6$P-AD^T?)|?$KaTAb~0jlCTvBC{QyD zX;yNKH|}(b80hv4w;yAnLZE*GO)P@O3N1?s1b~v30X@P}#e;i57Gwbei|&Vz&Y+0d z44gtnuCYY`1YmwvPIoN0UjuNmsEKeK+&!Jzx#HdYD?U&O0gU94mn!7sn20ZE;ZpKQ4b?_KH^4VQ z7cB6Nlx#`KacMs32S6wA0L#G6fCCGWw@Tm?C9pB-S_az%i{%IHLduY%E^Li50&&O) zjM|1#V{qH7L?w>vCsB&!x(wDAFeyqHmY8?5lBi;^x;<%*kPUw%E{1Y2JzOJPg0p1c z5iSc5t0M0aST_;`5SDnxtxu0kkUtqIIn;B+b1Ier^{m9s3F?_u;|2$rZtXGHeUP@z zU<~C%ID}v^s!=$g5Qa`f*fvEU3yn4gW(7G3VyT6As0&I9>P+(%3X2ZbBbpiQFQS6Q zwF8c4pzKkwfPa6$uq@rQb8Ux}!F+hq(gijJ2vs#^BAWBM@mH!>$ARR?6aw6b8(~eosi4sC8@^Kgh8ui^NE(M@p0_+=O&?C&xgj zgrFKw^x*b~o|aS05yL@>1Nceqpx>~t<)M*)R>_bMvA%!wXb|7|F?h5Bu%S(sJEF(8 zj#T1MH3@JeywA{i)m&^boX|3q_>Mz~dkRoU3^~1L$nivx9>X1*;CI&!I0tE&WO{m4 z=1S`ZRUVa@4EKTye<+LS9t5ffkGuXLkretuh+$}{5YAR|g#%rmyf>55^KWDkPDvsx z$axb>-!^}ud?j)WzPM6i6G`g1C#58xgwT|%=1h!qF0OJIvM`?g6){Ay(aFj5WOG6` z)AWFdC9g_ASCh9{RW#O6pbYm;M$Mi7k`qZps+t_}(1Xi)n$v&!4uca3q+wwk_*io! zvl^L5&wZKShsX4c`WI3yC!`UTRdj;?HXeFe5kr3;1On!$g{-C-PdYC4Lr|8qANrE> zc{QV?ia^bx?j>8ZI@~`f$(tuSDMfrpqN|i11uCHj6P-v7Xp5K{q6SEkMgV5qhm=x+8X(yk8lQdyy6xBV&K@ zwG3^cgt*ZtFA@i26yizUUpXZ0Y$ziq?=5KX)Ohy7z5b>fUSc-3Nb*LTlp&M|8kCYs zFi8Qgu4YNc#RuqCg2N30Nz>gphGyj4y8-eFM#}x6Mnh|)G*(10gW2^Z1wzoy5uB3Z z3E#w!ZATGjG})ezr~|gi%U>A>Y|Vd+3>QBs72i@Wp6EJ#$)u*iR8yLuot0Dr1xyjk zH9&4#Kqf@VO&AQf1Dd{1A}`0GMVMo{WnjoFdG2(bp$>C>pCk%^r^enu3rs5IEaRF1 zEgHhJFpZi%#=V2$iiX>CvLRA@Tdpr<|LQ=ReWm8rEaC*^3*43yHZ79VdXj%?o|V#l zKJpH$c`1s2;XrpZR3}2$r?QyFNz?VodolWlHd6B5faT8lh)$vJu{qY1(r!plr=}yI z8@kf>UVrpj(l3b%q`;Bw5I9^(6s2+pTap)W| zqT2=T=#OJFsg%Bep(=>;g2aC@h4yp2p#EEzBB*i8MJWfLsiUicyeIvRNKMLNMGO~j z^14+?j$fD#^k|BE5ymL%5V2%vk&>nvWvfynB^t_=MwR8H1WVsEH)K>w-&0YxBkItV zo~071ht5J@GSLv>Oq!S~T?zLzY(5&mMbA&Ln_-8bMfRJQxbqbvb%=iwB-BM!9W6fk zE>6j5B1*!vP*XStA@7K(QlRK%NTH&h!H0e6Q3T2`N_!eFHBKru5(4);SWHyE{h_kR z@vW4oEc!l9u1PLY@NhR;MAYBT*U9CfE^HGWI^|IiI|0tI=T1fCjCV_ynEV{pMg`>cXN;=}|n+&gZ}7N$p$L2`t@ zm%{Lj|58^WXls86O@tx@*(HRG0C>SKFdpTu0@qex7gR7F@Ms7pGI8eLa+e_qgh(oE zNT3ze52~RsC8Z3f#1+&F1;R+dr5?V+{f2D)slWdKEBzw=(XeI^%w^#3A)*WlP!EQH zl6zV~<3@hP1pKFt_`j)g4+Q5POAg5MZL>M;B#`ftc4G0Y%;i;{thmzFZ597r-4PE>hZfIA`h0 zz%qcNf&(puq78m-s4tpNhAZ|`f=zrlM3JFM2_l2IPU}mvqC%uVM1_Fk+=B|HA&q28 z2uO(NFDQS)P;OoYu^BJWxj{HnNYlciG$T?_zCr7d12Pz!3~D9wTjtUFBUUJBZc4e? zDUKPXc!6wcT}Q4k1(n>WMC=b^54BU!vm5>*d?Tt6chD`qd`yzu@v#nup)^BZtx@$p#W@_ zD7k-qDI*d|K!b^Zg+y3<5|IQLTOfeq5lJA768M4;NErO5REl5@94i6mMLhD?#*74P z6iP^-6l^-D6NEgG1SVu*bP+3rOG5mdp%B1)U0uU65P=e(T)qxUQCB+#tjCaeS@34x6bJ}d%rgiwSqs}LAQ5Q5PS z!RSbYJbH=)IfWI#7yKJItOk*YoDxb!2zN*lWE#UH;Q|&Kp;U3LZpDqBm$8D+a*4Y8DJ3o5}^VSM_$i^^->`S z444>^faSxZq61O@X#)SSRAEJU0(vj_UP|9-gI!=I2uK*1BmvR}Qk6i3z_O(h$iIZx zO52T)m5_rdDJzi@z=;GB@-Bjf2#|m9OC$hgkwhpGhF~QFDT@SXn}jq4KpRg2kfi}9 z;eipz`6BR80-ZoWSP!y`p9K=(H~0oS9tmU@kp(D#u^AHND7l;m3Ir_6KzG3rFzFjW zP>yee>kze&4TKEjTmlsVvWBn{;FKT(j3uI*l}ixDSiTTRft=8#?3)Piifw;SgpY_Y zqzo937W|Tsj|z!`>upd|B9VZL3f>g)U}y-6JG=woz<7c~08NOc06c-^g#xTixGVW3 z0l>r57zSYoQW$hH7>bl2VacDbiO95IA{fBKbc;l!NoM?#z-9xmjMaxVgpd7#o(8xP zqMsy{V5k6vy#QELf&)ZEN`!yVodJ|3*;Zf=2Bwk#4`Hoh)=7hx;Gb85IDiRxhz7EW zXDO;B^%5X)2}lW{hyX=^go3|!5jjoaJxu|^$!;aw+j%XxE@Fd z3@_pL5GWN1_Evx+F2$;`Vt{@W05cIOcAOm@ne?$)s3TOk61UI>A0!m<`&LVnaFBc_tCd2y5`y~_%M9(?!|le`E3tr-kYBMXG`_h4eYU7md&s1cvvfq!P-P#8~ z*x2b)p3M@or7wTG&KJKHo_+V>`OrsK+{aG0-FDLJbOh`B(9x<;; z%L&ZgdE;W7CCS%CZaXheOmAoCp*5v{w$p@;2~J+Yx3oGIg>7b>&j>o4I{ac~-nrU$ z{chjzPtJGgJMkW?b1S^pWbfHkE4zq`o-YyHwjZqh@|SC=*1jkp3wZebLCa#m5$flF50Glx^qc1?0R~b zW4!(vbzwp8ZJVNMgZ#X?r+F^wc=^Mha|_l<2Tge!nmR7)L`9dS zPkRS>n>F*>@$~jv*Ram@b-aXet^D(KjE@Z-v}S*6*7$+1lT7_bDk?kXcXqTZ6+h{= zrR~b_=;k+zXXyLQYdOL{X;O=IH=m`QoN>DFvGMCpn@hrn*(P24#7_Qh9xN_M9oS}* zx#Y;z>D})8uMOGIYJqO;#j+{S=6o^vw7Su@GXn%?PhCIhYVTrQKWIzO$`ScjvN2ojT1qu-E$7#YfA|&UeXmIvyNk)%vmL zwvS_uh5mf`V$XHa6Qjo6-Z|`Y&1zs`uGHJ7-wvlO&~V((55% z9T)FhJ8*;C{P7#!>&hNo-ubz&*H2pYkSXai)UW4S^B?1`r?Yc2FJC*?JJCDmLGq05 zo2m;KYwzLCG56oK=xUJNd3DL*vOUXUygn%wt6Uga63Y4PK{0M;=867JNIxpVzs)5 z_16{C*N@rUG(PL;NT*4T3p$>TShsQ>`{wXZ;qB`e*7!{^tc@!O+tRj$_oLwKOG}bR zeBI+}e{t&R#u<-|I$hhdr^o5Z4s3sqEwfHH(w{4wmNO@LX=sLCxNz?1twUFrxwLQg zs>R$#vwJ6cwNfp}%rE+)=lA6GhdIYqFKfQtQr-47|B8w6KN3CF`gf!3l=_GMtYMo%lwsO^YwlUd2fOY$X41~fjmBQJVon2+gjJBvL-Ziz?awO4=T4DHbU zZj2UNk?b^QUfeQs`=G5sKf_a)Z5_LhZ1*&ELf(z2ua9eo4jSZiw|1Dv};u(4lTw2!*^$TWTWj)295!9Pz_)E?3fh`BZT ziTjT+6&FG+4#cz?9xk)1x_f{AT5WIZu3OKVt-4cquJewP>(;l=%?z}ja@4hO-Ju&{ zYwbI{o_(=Nv>Rf7D{Ay2p%>uuxPJ7vyq_FSv%7=V(eAH*?`Da&~RL}3z zx8I0!gHnIC37v6||2}uqz<4$wV!NJ8rf;`v7sp@KHCr+Cx=->gi_U*DEBeL$NSzX= zeMGWBIN`(nX7TQw`RgsF{aDa@p!xlx?v~jGlF>#+&Xu+k#BQcR;ZtJw1w`2LEx)*p zYk6;+wkod4v!v){)A%2Ferf9`nRmH;vupL)J9qmG{vh~zYKFJ%vQeo^+cB=BB**)M&rj?U>lIC%c( zNtNT5rr)gm{=B^X)|=ZdX&JrP6J>91m^a#?K4@g;&}XA=IEZV`^tSLm(d=OH?C=dX zAw|0;WEf-~oYjBL#yrUC$?m!x0}}4OvboY+)%)o7zDa?7SIiZpFWVs~9-`kq>Q4VB zOBY5=+VMF3pLOB=Z6;UO&*|GpFm?UP&&ykl>9{!m-I9HqZBF_82Zqo9)AG&c4rya+8uUUUq<|h=~)*ZW|b?rC5ZLKDx z1`KH96BJcDU+1Tmr)d}3$=9qu zt9QsL^-}G^{wMTbj8zWUdbGPsBezvc7KX$mG`ly|R3?|(%p84x=rQ;Dfu0Nbr`r26 zd*23Lc~j>bx-9t7wL#}km9|-UangKq?dqWph4-#~?D5H7cJx@m&C#w}^Q@OyI(l_n z-e-UQkJ)cK?(^*ZUc7&pO?xr_^@`wew;HkM2b|y6^2W^pi`_?9o^6$AYim7P}CwXMjj9~e6C-1&B~J$f8zzHZyg z`eTLC5NV?1@%H*dYef?e9#$-3pC(-QUr>EDq)n$;b%xd^fvwUuJsP@CdCQ}4b*{^d zZN8>G2b+%>?ULTI)q=4m=SvgaSKZvcb9mVx!!^;W_koA4FLvs=Gycb*&;@>Of{=f? z>hk1Mf^j1+&rExIzprt6%z^nUw2v!nMZM--t$UC%c*cari={Vxg5Jhm5q@dg%>UEA zg1h~mwS7OxV@qh+_Uz2p^~1yW9KF?Kdg1Y}gF`PgAM)VB?O*%~v}IbDCPTt+3br>!QlTTXC3=i9{A{jJmbj@KR^XyRphQ@exliisl*Ui&b< z`{Xh><>FJD@0^G`5q5n+U9D-wL94@$Ejz8R3ftHF?#w5hUmja}ImuvJ zy<;(3`6*|z#BN2ee`2j~4Eb4`VtiUsYSba1&hyx*fSKK<-k$!zZs_~PDeHSM1CEsM z+Vn8?+g(pr^E*dY8qL4n+Q@%u^opvz&!>Od7t43#JuT|j=CMUs)aeC5TR%tNTfD@1 z&5Ob_r}V}xFt8NP>KAlQD?0qpt`PCH<59=vXHPe{|5ovWt&E8aD=)e?!RlvNMe>@m z&wK7^O%Pkg?7s3~#-8x(l*o$h`wmTbE!T0CuU>4FQ2jkGRIL}$<)43&{%1@RW4A2* zdZ5e8o!VO_w`e|1dHl!bZSh~)f4i6j%OZQ0t>dL=&o9bad*HxIzn=nj zyy0RGk0Y}Oerx7A+`!}dvb;ye!Kd2vEdPGivvI~z!!ViP?SwCif|9TnEuPEUrgtez z&%PA%tLm=T%t<`G7?axkUhf^P3ugv(TY5y_y=Uj~D|;-@395g$zHFZC-DkwBU#|yx zZdu`GzAh(oTB&E;itZhDTKKnqsmfc!>)(I>${dwP zU5Xzupv~iL*~@v$PF1!sHb^{h+N+DU?F!@XI=ahxXL?`$$N8F#=cCKt?jL-wzdE;Y ztDVS4^4)f1LUn%&d+)8*Rksq_O`F+iLfz<9NuRymw43w3yYCU6JgVj}?{S>(23xaV z#v8w-W^Hsycq+_Gu(aQOa*Hs2TJXIM%?DTfS|UHYcw64x+%y{-$?YHd6>S&V#w2+@ zFBg3@JjoXBHV$nO&x9p9C-iHQR2+Y0!n>MnRr*s6j*fpm@T}$1qv!I1-20mS&>H!+ zZ29)Xhr-)Dd=)gsjD5ymsOWyc`*r_h-CMzDv)Z|AxpvycCFFxiC0{3NyY-mWUmv9y zSGLh9`t^_JhxHq;u}5#$dtQ7UnXfijl)WU!W0B4oMX%^P`cn$p7$;u77CDf0>oLfp z^|{czXSsi?gO)eXui8I+WMcGye$KVNQ#U7^KAHbH<7C~_ltY`Hoh=*J6+1h2xRezf zv|&tq_OvNWrg*4$e*Jg27td~;Fe9@`TStS}(g^wJW3y&dJs8HbZs-4so%()aqnGs` z$F1+HXZU#Io}d*MyDrtA)-B+?PU6K+dMEd9ZFPTR>5w;r`rrS@xKqE6j|YdF#+S`8 zUAMuYtnx_zO@XLYUX4z-Q1o)Gb`4M!o>Cj<+s_Z+E%wEB`j+)|!1f zZHr<*B^O8%iV{Xn`aWc^Lx&o%%{uwBm|bITeKqjh(|_^fV7I52Z|nNSWj!}psyb&r z@Uws4yZvk4zRw3+ZH!$|HF#dH z6T#w=%D{208_nt+?C>N||8vH*b-f>^M$T|Hjybbx;l5h;5rfxU$!g}((WvY7W!YEK z1gj-er0?5)%I^JbQ?+UB^ujHvrwiiZyN`dhI=1^|kMl=j4vo%Q@z51-&Uzq#rZD@B3qnRq>*Jr}6dv^$6D!sKVe0EzF z@hpXh4oW8~eUA>`IrQPCy~Po0Ljn$@L{y&sykXr2-#mWq=}WvtpW1x9xo+e5_%(kQ zliyW*%0f<4FD7Hj(xUy3ZnaIml=NdjZSxnaR)_XBt8uz9WcuK} z?fb`FACnfj(Ps4RlNT2b%}aWu6f1!JBw#4q3E^~%%zIi~Obe*N#)|9*e{@7MqTU%RLI z`X;N(-(`mAoja4bDR9B(>+e;5)~glUbup=raI6TbemHvj& z|C+^a;Lu)&dd)O13(YW#TrpzA`4x`pgIAJ_&V^>oEx$AjR7~$Rsj)MV^5E%v*HI=wg+9oOeOkrgxVUiKS*tn-VKaZ&riI-h_2TDm^)cwwuR(aq&Q zRU@Z+URif0dvPS+g!)=tZ2zb0iByOSThWgX?x~lU{C;Rdo|(DS=vabthYvN=7d6(O zW&Rh2a&et2u(e0Jl62i|AT1Qlo3^=@=2e^HkmD_X5@wQfOjIW_r-l{lyM#d&X|pO4n7 zC~^q^-FkbwH>w8vI*X4dUKmce7?URZKEpF(3+?&ZryG9{o8JEJxgF;VWoAD8oo){a zKV%%B*WCD9!K8M#an-ej2(6lCDn(1zCZ37I?nr9k(|e0?NHc0by7eaW^(7rR?U(&3{Z?M*!l<* zlbK)1naiGOL9t<>Ff3{#h z`}$_afjfs9<(@fGnlQBHsI@fKX=lgfCAt5spFDp#xGu2ia-Bi5h}CynM9;s`t84aj z9aGoU&G5qUi_S-;NaxL&xUOf^g6>9FPL9t~f#^3GH%HJ`G0L)!EM@!D?hzIy=VBLj zpX7J%Sk#2U4i|!Tn|^^%oL$mu|0dZl$;!Kvlcz*q4x5m-H+O9bC;E5(qmhf&BYbA< zJ|ll`SK_&MT~_-huMC?mwB_|1D7!an+Wm{2v{!!}JpgUvq|3bnIoq{r+=JHjOM97h zW5egUS#5-4Tb(L7YGI_c(em$7p2lx1=zSc2b1;-eZW#p+czLimcfwf+8 zT*Ut%4!b|MQF`t;3tT{SVuJqXx3Psq<(7XftjOe#HLLu{&X_xH$Gd}bzCRq_X@A$$ z*89CoPmHNc1>D#H#zdG=kD8_jaWu)L`>Bvk6Arx<`;|_I<39eQL-sGAU(RK znJRO`PqXdkD%$<_0UsMTaGzwR%TFbUp%|r320<(X# zV*BikL@8GfeSU0QLCyL#QOk)=uZ?ohzm)wnat^an3;^3*r>gLH`(pUr_07y2*Elbi z8l3KybT4X&;r)`Rw~;U69a7R>tIE3Yw9eL+#(oLJ*-y@{8xk7R%;TF~b#xa=g7f#k z+0HY&n~~(xg?{d*=MFqukU8P#=<5bxyt&nC!rO^S^dMdr6zlcPG&)^5SssZV{+ z+syW<*%%nV*+qQZw^X%fe(9HI)2urNww0_LU8;L-MR#3oljNKaPqI5DuhxX2B=qct z9V7K-X&WsPCY5g{1*(i1c_QhW>+)d>`}LjqWkk{%>+6ZhngV6k)sDK;s@i|xSl=OS zZ|?k*Y4I=IcKf)g|FHJ6R$6WkY&>%Ig||V&tuu30Y4$yX??gm9FUzaV9UyT0vUYlA z%VwJTm5j+6@U!6H2kDKUq(F(` zVpGc)#r!>%PJ!LORL0tFjJJQ;q+`6|&Di35VH%naw{yl4zqo3eB-e2A?b zMus+|2~C3M5B@qU>+Yw%|M_*X?%>Q_+h(;o-fOXshvlBBqF(E1r?fh-J$dBe+Ak+R z*#^8_e$Td>#Nt|n(LBHV>re86Pu+XI>Ej?9-Mc3L938d5dk{?2)cb(6N4tkR_oN;D zIOCR&ZvKPh@gn22y^nv2&-tHEdw9paM{b(&`M%czuQZ=?1{_&z=YQAzr>|+_B~~5| z#GA`^7wu$YM48)p^Z6N@-rVXq_Ux}&(e}Exw|2&Y0%8-a-DxI-P8_;R7zSAknnMy zpBKh*1ifDMdfe+&tr^Wuc^Hrae=cj`R4U{xdr#3xbUo8d;1^eeo@CkmIr7tphpy0xWM! zgyVP?AD2${D0NOdz8lKbbIL&W=Vikjp9ep)2Q8vA%fk^C;ig? zxXaG}QALea-u$J@ns*a8)MfSii4-wPkZuA!^n&2#iYI?v!W?it2T;XeUs?AAO>RhF{jBX%o4Z+2S0c9d|*ov!ZNg2Eg* z^|5pgWw;mp4tfNH*S08lIc&zSu|H0GKD;6`dRV$L+rv_Sx_j8qH=czfa+8)WTpc6S zEh@6_y0o?K`ihrazMILZ$SPU9ZLs;Q7d8VxWZktNHgwR=Yu(1{1>Pwf+~HQq_3g%E z_5mr*>DJDC^3VJfmmTd|dDQ8ow7XSB1S=7hgGW?*Qbma(tFS45$KB>LY}XE%x2DID znEE|WBAuRp=VT?1Mk&UN{}SevyR5ElS?bHns(0~@@f|CU71RsuD&MADaCZsKelz|s z5oQyt z&2Ng1PKp{_wOk)HLTb0_kGXz72ZZ~)?Z5PKRL?68tsS$_`lqXpl9g5l>~FrVc$ZW1 zrI}BEm^{NbiC6N|I%H85cT{|-+s64fW3GmcUWsH**s`hTuRrokS1`ZmVq`;EHRSsjf6%59bFxq)dk+5)~7aHddbpk>6<(ig!4I!1hfwxI!B zHi8X~Py#z2U7nE;E{g=ZdJ?gggs;yN^0c)CVqIM$vBc0wq-$iTXDAfw@byJn`aB6V zEM1{M&j>aXA|q`+&p-$`5el{RBszM3da#C`NTkIV8%p#fA|MPc*i4AD41{7WZ9RC( zNGQ_f>Few00bv-34fVxhF%R743w89xMtUM6Be9;Y#6YC0qs!OU=fMTBP#Z{HS0XSn z)B=Id6TmYPv4Id84FCWHxLV#X<>> zucIU2!{mm#5}vLW{3ntaX=(Aa!6$74p1y%ZAHE$q+$i;SoA~vQ^z`2L z)cMj;TykQ|?bFv2vs=|hhOg}W^mLO^X&vgt4$IRgZ@%_5S823)VxIwjfr}kF*VG2Z zSx;;?Y{B!%`!9V;cru`1|6TjarI%mK8aKXC^{ho7PHVqY{Bw6ea?|lwY9@I+ANH;? zTc?}-yqegb{EBtGeH=FDn6356eKx@(XPs8;@Zqz@rn~lByLCp@aD(p$iylX~uU1Dd zDr~8I)x&iA&KobLMq7w~$B*`NTe-K7#mwEa8y#Aumsc32{nm2$hmSUss|vi9?Ee0E zwSrN_V5({TgA_oHW?u--dHQ$@AJ#=w7Ti=p}*Rl8O=slON8co`G`0LKGXByeAxHlqlQiNOAD)IB?w!iM0?cC@eaDKK+ z=BP!pMvl?nI)CBYn6gvu3ylXkZCT!Hjl+t`mP1}B&)hj%G%>2YbM$JnC93rmmKifI zm>r*f?v(t^kf&FFqWiZ7EFPH=5P92paI(kT^5!+A&HB#2*LP#PR^b`d>#t0_v&hw6 zeqd|Zu+C)_BkX6D4vCrNw9R(@y66u2p$FU4AJUb6=@)1?Wp%TMt7Jy*^Hz)N=G8Y2 zvCVNy9k$`7zt+zIJGWHze&&!~uVpgT1FWnWAu;u%n@10U^`+Pf5*~%q#hQqtXmZqy)+rB>by!PvclzVS2=al+B z9U()}ibQL=atKQj$1YYgY`{Jvl=iw8xh8$|U)xe}peRx${1A`0B zxf9)g_wv26I>~n`Roxf1JslIIQ+~^-i}$zuVXKaQZe-BpdfXlJ4jF^%*1R~lcgCrJ z-PX_UesN7ad292^FunK%t^QeXe9f%p2jy8?Uyi+Rk^88|U3X;UuUL1Jy$Q#DKH?o+ zoAoXFZdIkjqQw`I7Wt2LOc*lkq0oK&%~KbDch8$S=}@htz`9@WnFE^lIWw+zp!wtF z73x`OBeHZ~4e65FbCP&|Lf=-+EZ4LxfBSuop?eDd;q(5{+v-iPZkSPd|IQ|N8}kKm zC(={gdYqXzAm(A*_wH>X=dFC``u+0iiA&h4_R;s64PGhA~c8q{i9*rFCoY`lAIpFUw*!f}T?NkLbq?4E14 zuC?+k*o;m2pypi(H8~fcfJ!IF|NfDfqX*Y17 z?c-yLg%wSGE58gsvEz+@#Hr2Wb~^slt_ymz{X)zBOp`<%e`VUFfKpeTVVAytFZH~T znlq!&rSpf;Z!bKzZF2BTQn&rpIo}*ZtF{|d@q~u+f`8VBj;vVV)G=cG%7aerh71~| zx?m))TI_9EdG2MqlJMsiQ9mrhb~u}eKOef}m#+8xn5-Y~bq7R$Y<7;Ga6)_f{par|O^JP4wmmxRThJow4oOM=20bfp z3zM=EX4URHDc<0rcieO8>@&%!UV*C?HJ6RJQ=kMyl}2vT+){T8z1f9wJe+yb-{SWRNkavL-MCK_8o85Z1siM%CPQEBDd3@ zU&gNOcObBJwEV`XBTKS>W@mZ^wN30gt@G|K177e0zNGjb+aGr8#MmJatx@Z#mk$q` zvGch=`TAJ1U43Wj9se}1{Ul{rOp7-sy7vq}Hl>a6rgmR9^Y--b#CJLM%A7s&>RMRu zb3LQQyx7iT-hAlIoJc#Y!>_U z)GAqFEiUr!w*0Hb8h~J{P?OzS7hCGd-vL>_xeZp?G~BKKJJgMIBeC*G3!#} z$~2v&Z>4jZd<;D))sE|bp&~#|kH zKO7Z_HqVpTYvz|T3^O!i%ZF{#gbM~2y zaz~9tzu~8AT-Gda)3xSv`N{Zsw)xxJ*`;2YTie;D#Rh|3Uk_fLkm6(?W!vwV!T0;2 ztF8EtZ<;NC*!gj*UH48pE=9AyMy8CEmu)pvm=DR==yuv+`&IptM`NDtYPA2Gcd=3S zo)(b{_C9REYt!pab>OtToZ8){g?Ufh|(5ga%|S_`mSFwbHk$pV#DzV4}`>j&25=5+doPF-bP=?^=|8z9#W^z z+}g*rOsmtxinP3`(Epu(^-3|S^dD~6y}*3Aww;NSc34s8u`TXZo{}7$XcD}`Y1oeT zKVJ>qcY9TAb61^1CZap%pNtCnXS>OV8Gd5|BBuQM^GKYI7SDdw<4WEh`Qh?GZ6)=^cG1)Ey=EO5)-mvdr??f#$3TaIY|@YT?;_;U{<)bG>gS#~|QazR*A zuMv;i8;vwcvW%N_qnX6}py@@2Na6U&7X0>qHyMldbFWPkFp4FCvgSwMm2|OPZD4&S z;=;;g95Fw@zr>r{(O%W-S+I#SUz-=gsR)aZkhYm+u_WC3Vb<9v)N67e?ATH=1oz z9x;3J<&*vQ%6_cLv*u|B6`5Y_o1<#fc24cKWHR&Bs2xnp~q?`^MSP&du&l zWQUQ{JU$Fw;;R;~DQlkcz>F8VNxy}QO&1@*k=%ezKPzMbnNHn? zd>H>BJw?`R%jgXuX4{2j2gUvmRmM?;-7PvksWb8}Sa#ya$WWu>rVDS6vss*fS-m7= z!ScWnh03Yp#^tY$ic9MJ{o~rWmm;TlQ*B*c9YZ~_wywy~&``&aFEljN62rz!q+|4F z32K@+G1~w(cp{Pm##e1VWdr9?|dOJw->mVj=5boqMPLOmmW zK3^jIvy18L@U(dXEO0$Tu@>xqskHSZMj|6Uo|e#vXDBh$7Z^$Y-TRHiMnZi(9RnS) zmX?9Oj#!|tr)9{~(>Br(2(@%zJ$-Oe%aF1L&qyLM{I_h?;_2v$c|3h>Lji8JgxcD| z-?VPfzyjI=Bb5KY7d85eihpy7p^lMIOWRPSuO) zcafolCovR?cslx)t)B8`i2<)`^ z`VGe5|4~a@N8=WeNCE?$e~lKv7FbJ{gSkjsC=~Oxg*p-=9IX$qGU9|g?4GrC`2Y+< zBLf35-^c*zwftHxB{}<;-^aKqdBHlnub;a1s4fzrf zTZRI@mQaWH-^hli0~z>lWTP(;8j3^)hI~UlPuEbFFE--q3xq;_0YHy30nZQsMK<$1 z9$`;ijcPZLh$Vm-Exu5fFM&bYVv#oAKqxfS`A>zTBi08v>*(r#0X_|l^m#l3U5S_{ z6!SFQ!H^38Y~M9xATksh>FDY64Rm<_B36Nsh_7uV7X1%opeGXW^hFW~fRUa^=U)>- zR7GL~v4F2ZD^+>_vdZxPUGab^+D5wCJc*9bP)ntG;D+Wo3om(p3##R^*>m z!|(J~x@OqlUjQF}&94NHnhzQop_v7T|9=9YVM$!i>W>d-BorBt4gd>8jY{=z{Rz(y z62^y#WYP@&kFwF2^RI63yIHHKBDS(3dlecaIvZh35;6SrC!93Tkfd=or#B2lo`xS9 z3ZN1GPdE7@Uqwjcz!f34#2;M*mTM5s4SBLcQ&rS|CMytN4K|TQA;d^enhe7i z2r!BW{DLYcKM90YD(;gC7la?28iM&i^TTJ_IsO;`wE;70a?yYnm5Rn#r6LqZ!~47R ze`j1pMH&8%qU<*iRA~5r!y;j1`cy+wRVs+G6)Kef+`OnX3{8hazoLVI4;$*iswXWF z&7xKsBUFTcms8=xf2a}i)W3jjMQ8kj&$US5{1+vqLQ(%m9Pl3w{x{ttpimO3pg&W* zX{`Pa71+QJWLDVRz?==DRiz>_-AYATG|g^9izcgshE)9z*~KdjWrWcp215g&OsP_F zQGuACY=bdGBvU#8*+%lx;HK5@7!#*^{@D$=$kTRzi(3u6V3>}X!EqR56>;WIaKN3v zHV`V@uKa-=2;#r|B%?X|sV#r?Oe)hfW68r9_J#*&#i~@Wp6YLEP^l<2fNsaaOy{g5 zr1K{)|4GJHsy{k;I!SWF#sos~n@IognHv3GXO15SncB$5jmrPq$C00qz8e|x`4@;*y1=2$hdB<4JB>>k z|9GPd8pZ8(fm{Uy9$#I|ck?l8i{Wmx5fwClQC1bOU*R_braq25fN*B`*gE*-!_D|^ z*m6;4fXr|y_Bp=Vt&tlM#%NJn*y|J@KkmjS8G_LubdK}+tB>FIY3l<3;1{R4;o5He z)s5VIj-yM(RDyp*6yD~y%>s6bCGLUToY257|A)Qnj%y-a*OSa7ne-?iMFJKS32Om= z3*xE}M8$%LA{LYwJ1Rjoh-mC6i@Lgk#s;FYVp|t67DUvny8+9_iilB^qEalVASwuW zrzM#H>Ym*_=brn!ygy)OzWK^qpZA%+I0k}P(IBKjs>9-Y6p6mVAOGmUQeXI5t(K}V zSVBoA*5|5-=@<#bJ`beckEJ3czl6wtLLIuRDpt2g>#YEP&%l)sfHFs&9jVli4kQ$j zD6ZQT#AzT}1jr1t43R&x6BRS zUZFgPV zPiu8TA{X5do+R+b9%Ne7D1}Oh7QPg7h^hqrAVF(K1!`5OL$F+rwFFftx?~7cXbVT} z*GWr`sl5?A6@kG*mA?vY8=&AEeu@_D=xCB6c!cmuoDAT2q(VJ{93#*N-dluXijs>l zOsN8Myf+#bDYBn|dkds`U{a!gb)d6}$E!w!=&5~;K(-XaHKjDl8%Y;1S*cKY>+Y(w zNa6@(pfp*FGI`Md913^-2sOjpA+u4IfGVVeskK^ytAaWOMs^%Q%o44P#GNtfEK-Y= zc&|kJt5l1SP7sNM08GXr<%r;-R8%EW2()@Ek~*jm-S8F-VtfyQGnE>DDdsB}-^0oj z%~G{Ws_&Zw?8<~_^AibGD76k2U`Z8=BpU$Q$P`GE!C0R_Ezn}rTI3dsRCSfkJ4$ue zM&>^cWj-kJQE5>(st`A-g{oMj3u~ivAku}U>c;|`Skw-o8c~rhmFhqYp0rqFmDWpD z$PQ`|h<@r2ya!DSL&Ft+p)D8-qe7+)Msi!I6(CrI1FyOSV*?7LA{0`Q3{-Pf2z;s0 ziV&!<98{>$4&;VGgHWY(!$9&e(k|7>1T?5JFf{}s9h%2vHI%_JtpZ(vr8~x3^e#G= zjKHA45H_XQ4PhtJSdm($mHDH-LQVz+>RPn`L~!#oMugljU#W$ERk7YG08RGOgmXWk zd$mZ5v`CAz$bU$Reo+K(adlhSP@3U#VoKld+m$}_o1J#5zp&fWHTN8c-c1QVzWRyd z9A!~RsE~4`{HM&y7O5VO$f0UP_c)wdSDof~!XK-V_buESqf@)j0c ziCF0c=N-D5_pN_|yH9F*E1~Z6h=HTW?kt#8;WfXY{`wk!&uNSm?^W#maKq4dE4;^F zZ7&;D^15WA;C9OCxxW-0cJF_qZ;k9{ug>=MZ#Py~x8;fss=XV|T)*LYZENQ%wzDmw z57PZC6NCrn{;_wj^Mf{Hb#KOwyy$k|@E^;nipF&*zFsw=SLT%XNb1^zU3T{$&DtN` zW@f8F8~SvAKcASAf005p`$Nd1hX?<&Gam@SN>8|rwW_4l@&el((aY% z2itf`3c8$*+1NYb?4(P_+Vr2;)$ORih?>2_-+3ELRiD<%%S0(oD*ygOC}FEnwB5rKSwGK+ZSQ6ap!~( z*K6nNpIJFEpVRucDU*$H{=+9wU&J5wJH zqmHhB@u&A0(%n94a%$zB?7=tsPyKm%wO6Uc{?qjA0rDAZ+g0^Ab?f8Nl99z{#=6~I z9UfZVPt)Bs`QhwLpQN2dk8@gU^V+dTQ^Y{~EChW!Nv2mc?ofCFLbz&lr_Px<4u?Fefe{}Y`4@oQOEvN zRJp{i&uZWI3!LJeuEd3|+4}p>HA}lG#P0+*5(`CbUQEo$y!_}wuyt!*AzYPQ+CN8s zWa&Hc+R$TrKCv5M2gksVGsY}xZv)*KZ#H3Se7f_=7pLW+DS$R;>IAbc?jKW+)J+XX|qL@!rCo1@*~JY1-FzUh7|Wss2W?WM5=opOH5ocr93Qn5JAb zVdH>%9u4&|^SgmR%o%tt)iW>kO$z9L;&!qPv!=#-i2a^iSC^a_X`oAOeu?b^Y4YQ! z_3wTO6P|oJ#+uUgQLY#BLw3v!b%b4DyGfbv2iJRiamIgRwKIoqwY^98 zX)lC9tHunN74aCNOWvnl|8Z|}fQ9(dm3~tPY`x`__*ye=O7Y^cqRcIS%TqOpVT^*+ z$%z>trXhv`BJpoD=`$qWRTvfiSYS)WDYTNlnPMzsnrRrT{V|R6N zKQ-^PuWEBo-o`m4K9Qq~Cg~1KW?h z-6P1q?Y=j6({FvOEV6j?F_NCqI&|j(s=ehqyEAiBIv@V_-o=D}?xUjjm$J%hp?2$e zQT=#MW$t;RxLd4Ka|Vlieh5?Rg1?j8urMjt+ZG`UMGrSB8w7M~>{b$2-NK_Tsu5^K8;&R>z^Xq#!i%|2v5jXpkV zTgKsKw=3hOUpnQt%%^U2pMai4x7|Hb>CaZrNJ$jlzdp3Xo|^4WP9ft?tO|7LTA#S; zspGoP4d#O^SGw=Z>mME%s4JTEhezQrFQ3hL%G)+)z>gb$J}9OB9g8D_23HPG6TCW~ z82l`LG?OY(2L zj2-BkJtAy>Nbf`SlC|dFuRmw=C}-Mt;DvUZ6wa#ip(z*J9J@R-Dw<|qqg#5tS7G?{ z7lW4VIBsJ#X{qaRmj^=IDe7_R5w$19 zq47L>pJe9*?V0ewYtx_4akzR^ROc0a|5CQo+iyC5#J)LG?N}6)vU>i(@hg~t`vxzy z_mJep+wOX`F7U5`ejoO}X9xKY6FY3*$t)WZZ{I4W>!ps%^CSI!)-Gi}5+{q#JzAa)y>nYq z*mX-?=9z}X*ucIq=XG{v_np1O#brIiJvNPh+R*;|$H?)oC`GHE%;s`m9h$K1)Rn>` z%0!X%Yf)eLY!|PgeLS8!iH|PqvXv8(8T%+YKej`A(TEq$vy>h07aaL*lO~t9#==6TDyJG;pa6)**9LNjg!o_{5>G&hX3?z<*PQF*_ZdHv~@Or z8#Z0w7*{fE=(b6RtvQ$H%(J|&qwlJ|&y@5~wTgJ!>E7nNb<_8jIu|X!cDlm=Mn&(8 zl7Vlh$3+*gfAtyJ({XqIN#2!@7dWJ}X1X6*Szfk(Z{7M5GwY*Q9b&pOK>rwVQ2woA zbDQsu9PE_z|3M@z(jqO=A}!J)O++Jq*T@l>B{vE-g#OWJG#2tNtd-+y(aTrQu|Vfq zV6(Bm8VkAp5Mmb#d=wwIkmIl9s1n4>S;Po2SwkF+{pfcg4$F3K0yAVq(G=_1F z-e`?+I_ZvduQ7SD(3s4UYyP>Y{O^gJjd&ML*3>|TmRyYq`KA{qao!vfm9Y6jWO5tp4JdfX$%Gbkg;D?ov5>Py;2$iSjp*N3W9nI zNY)#sn}Q`NAqdNj8DM}+Q*6Y4Hn$+>oyoSw&Ht|5WHre9>l+uY>YsO(!JJ0H)At#Y z7yqW}f8NF~0kOsaxTa;RF)C~jVuEItaT9XXG=}zR(QLGuKKvb{j5}iV!qd|-(bLir zxnv=iEKlUdQ!kdD6`r116_%cs$Zr;M$->?v6DrU*75J=w<#bE^{6BMlgK2`CRFQ>g zXVeONk!4vyj9Ti4usehL71%AJx`_|}Ca6{zPLd;PS$Yol{G1a^$NmY>M2BC`D8%DHEIN%tA-1P+K35bl z*mwlOSFjrmf59*KG9Ipf4CgZzf*dxV^vDO1xey4O9P#Hm7lXwBS$t}v{kd!^MQ;l_ z8Kx68XL>Gf37z{HEy4eWH zZD{&{UO670+1T%Z#&|v<5WpoZ|4MMl!n9~Kz$K=#XkSUq&(#~MAe+bGp*}(kh%KcVB8Vt~P9sq*NMb;KCPb(6(AQj$s#g#> zo+rnq)0%ezky*%p9mB*L9^HTe43h;^QjQXu8#U#@Z2iexHY7(SsQU%P2aX%U~d$1L!UtG?JSLR~H30%dxG;+WQTaxtjox7a5>mJ1!Z6j)Qb7=_VN=G*)@PpahYEAK z#`a9%!vMMFh&z~pW;l=V4+k*7fksZQW-RIi^#|}BnrpxkQ;x`G!`=AV5b?A zluv*G>5CSiFz9p~u?;=w%Ulj$$6#{VT$IZ}EFO&%Ith&^O9qF^piw|JnkEcP->;Wb zGb~N72s%m6JdJOOG~+=$8eLEF$;6GwObV3_Aay5c>Le_}O#;Pa8!8##@cm)0aMbvV6mk@rW}gPN4>9Sp*#pfWhNh_qAUY5m#YVVm;z(94MB}a1_W@}fWEdN z^j6Xo-RVYxfUbh&dj3Eo;I6a!{7OKx$^4LzF5p?nVZX~v~6goeMF zI2l}luVU+sW|Iq-@EVT;kh=pq0zd!_nU48?3Que%6E}uSGc^UGpoPb!@MvF8Y$iqD zUML`hTgql}@cd;p(_SD3Y-kP?4x53xjSA7ZR7$gb5#lqN(ncl%MMf*oguxFX=#!B> z@wjvU%fbD0zd3ypJri?m38 zi?m3K{2!D5VedV_qRO^z(VbK6plyOOD`s156ijVI#ej)22UJwdV8Ylcn6+$En;H`a z6x*zr0|v}F3r1|k98ueZzB$*ff&#VAIsgB^ci+9Y_fe?YVXe959CM7Z`uJ7tb{^lf zm%Dd6Zp!$?vE92<4b1NE9mnvt3{M_^JLYaBuu?R`evl$e7b92J?Xe1-L&7hCv}+r03xoX%y5@$T#nQK0dz8d&gc?T zx=ZQp(BG`>TMPZ_waent8C#cokGb5yy1+X1TE-~NP`wEc2ccfkT zHgHU|z~A&qGM(|ihJ5A9=Upiy4M8m~#&^S0{Kty%go^*dzR`Z!CM zRXI02+BJ9h!_)cV=bzv8e&mOZ%}*__MnBscQ|px1#`qtrdv4fMD16t1%E47HcIr}b zjI|s4=i{Au_U_6u?)OX2rg)qm7#?L``l^?w9@hPI!I_o!=N~=%`I+W_)TE6*4J%Y> zw(f!d%|tEWxAR$F-k#lQo&ITm_ig2ZD~hEWml|=hYoT4&7PVT}|5=U=zaKeKbRWZK z8eGkzU*+UOex(ObQ?{QC{uGn#)AFE3C+1dnPog&qdrypP9J1)*vKQUnyslB?)@RQf zBMwemeYL`)>UVcW&;N9PYG?JmpO?37FzW+f$3twGOpwr16~PpwbBi@dR?NkCDz%aap5{n{5CaiqndYfg_ zPPCqN_}J@Trq6j$YS@!@b-q;lQ(YzLxt9I$>gm3_^|thXS=+MD^Q_C2VUpiZF*ibb zOnkj!;+YEhgo-VvmUdU&dUTz&f9dweh1F_B?cp`MZOgvPedgNL?LzjR-rJFTQQmEK zm4|P)+-_KVv-{f0_iH9I4S#vjwZz-rfmQ10OFCZ~W8r*rlohtOTpLzvLdljl6Hl!E zb@!!f*A6^?zt^wq%CYNLu3CD(L`m;=Bd)C)@aZBGawVc@vy-EPH@vR2;6%j4=DYT9 zZ9aH^xlXs29pQf;JbuPc^2|*;PVI`^-AlgG?`7@xL4y7C$FP+8!!s3h4|!C{Zty2cPV{U$s@1 zQahS8$&$b8`eyAOU0C3{FJtDH@)h;wOr*959cI* zMFi};RAHB1wc@aK-R@M&61AdIiO&buH@;l-kCV#BdxhWKOgM1-*a(Vi{IbcYlAqPQ zDFavR?85sTs(F2}kmso~fAp&&`@Pk1!K?q`qEJU#t?U{FX1 z-0o?2^K*OP=LdJf6K-KqIJ6x?iHD2@+3lWTQJ!|pU{E}aYLAVIij7Jx0%xN9f-tF3 zVK7b+Pn^&0Nv4mAg`qQ~;j1l5JWSyhU~*%l z{2(I`o1dqjC)^idkHv>zy?(KOL2mHd4jHlAqp%P01%&JA2ZJa3c_za^FpeK&(G4PW z3yQ*l?D!W#g*)vq5M1)aLEu*E1ETVbwZ|tHfmq^`?NMQ2@pi3-rzf0r ztSbsC74Jr-^K%O#!@<3M1gHWJ8m0Nc+C1akir7&}VuL&( zt9CnSc~3-&UwlL|JP3)#8raFbW^SX3Ks`Jmc2A4|p%CwfsR@clY)07QV_?C4o<$(N zc9_>K3Q-?rKTQgYC?i;Zw?nK^eno;1Px0|EKAZ%Y$J+hiMLduPU;%=#;}ZbSSWi5^ z8vqq#_p9M)ckLTIu$b{!_gJh0tU3%)8;jM0Y{Wt$qU?}-xTwWEfR(xx@dE$=6yiO@ z5Ubd1njf${)*9AX1kyoz*^T_f#oO(`@@O2~2z|g^P%y}Ia6%XBn zrFX+xMS}Y??Jd;j=0(nNo2VsUuzj@-Fa0RA^es$B_kP0FI z;8BQ4KkN;>gpiB?FoU>8MQH$Kc+wNv8F5QUD9YO&ivS3+V2Bt7#+5DGIW!FpJ%KmX;zXkH_|`$Sd0f-Bm22$!qf{DW9U^48UHKItu0b)^} z@t#3Z@enBX0qTlp6h=kLygS|w0JoDlu$6+MkbNTHAzUxi`*f@ZKRoR*3;;X|i4Zyj zsUgfQ$k?ZB3l0Ig-xC)O9O6h!n8|Hq7%X;_TNprp8ZO!s5D*^(c?5CuE0T;A z2W-SZk#K1dSOEm4hUddTip&HiV>*##2)3a@p7yqWfI3)vm>ba_Ka9-N!5;CZN|93G zhzNSP4f@%h3|T}D09fP1v6uj|7?eR=7$P+aTLt5P#yH_zJgg{!EHXJNmR!M#r&l%C z5L}v+|&vRI3>N*Ns7eNy5b@o3{5j;J^p6D;wmtLpzvIV{mM5{$?BSFu}Hf9E!$6N1eIyV zR#7>NNTab;Nt>(bjIB)cQ2Ivr4q5nGgwnm&)7&b>sFJH~6!@=PkEEmV>+K^!G?$;3dK-d{j@LMjlAMysr*mK?87P8<`nKuRG?`{) zUS%`LkHRV{L;8f#X-(Hz$GVNw6eyN|YeOTktf9*4H#$kDQ5cQ%okX#;A&V5x(5@SQ z1(sJ$0?~1!puvhHMj{&p?%&vHm>UgRWhusx((g1_fi)F7ePMyOC|%X~Z@pz`=piiw zM-tB@NHm3Oqcz+#2s|U`hO2~V6fdwU>a@iWB#We|hDFpZk_Fk`snm|mk7|)AQK9fZ ziVGH9a>|pcYA%U%aS)oM7?yACW+Xv>5G*{+Ss2wpMqCCa)0hmsO7n`siLP@gv?3r6 z@ruryBqseEY|f{nc0+d|UE{yV<`&X*rmM(ET8@cCN@G>}Tm8rgtYGdp3|tf^!>Z`C z1Om=E;%E(xO7CbbD&+8cqQY~UW=UlXT^9|7V^~UMXiA`1on}RgDw?9g(GD(u5i+i@J#R8OIQKSxa57uH(dl#=uEh zHmEdjz$vapax?cFNDXNWsq30($_d4DhRDelg{Cz02$aOJh6JN= zyeMS!RE$NISc@z&oI>Hha-@vZ5@{BAo;EmIphZpLWLATYW@xy`3Oe}+Vcmct8XQjx z&`z47FdW3DbFwCLENZd{_iCa=ASF`pYwQHY0x^J9)^$}9B#Ea~-NG1u6ficQ8kNOR zbV^V~m1b00lPQK}Eea2Bal;X1hjT0~VUB3c5Cvc$&Vu?cX}l^LnnjZYgHwnb=2?c< zXp2TE;JEX%rV3YW=kG_Mmfqj<_9$pS4f7Rq2a#-Jr<0#l<>HGqVu$oNeWAnBZ> zS8KzSM&eC7Ae(GVJh-fh8sv~w7}JOAx*$Ts z1GOo-D3cbT4Uv%zRg+|3Lk&N3l|rwe5t4(TEF!p0R-`PVsDn3u5-Ej|EgY!=g;h{x zi_GD7dzgI5N{Yg&D#PGEZ{jElVUF0O$f`w!@DhZmSX5fpA)AmEN#S@w6ckog1;8nO z=cA?x98AX0&`&G?3H-9CJNze$`S=M=ioNgp&S;AtMH1PA!*qE z1~iS%sEp3(5Q(CH(11ci;bp@m8*r~hQn9@hnqo8&)c_Vt5j9{fDo=~FD$=ybsuX@% zi7XmUiI8PQ*8~C9sIoGCp{2&t3XA_dP#3i{nPezv9fh_4b8sR6oo96ziWUrk5@gK~ zoJ`G2ssugDNe%}IeBeNXpv$T%Dmu+bU|D5ABLzLoLwD(afKd*hOtWYQ3>q59>A1~2 z3)me{a{@;RzgdIR7z^fE(`ZIeAh&|XOL&`%a0D5oK*>?vWrboD5}5%nWFE_;@hmJ+ zh8Sg5)HqqiFGVobN|u3kppOJ(WeJ=NbPzN;e)=#2yWvM3rZVafCBx*{mqRG!_eo#u)gFg34QTiU#QG zg08Uwcx>n`uoNORp@KZe2($vLm060Wba2K1Ndv#}lacTr{!t9Yv9t*M2BL|PX)2wT zTTW+1)&Oh70|XQvx(q-l14E)U0+=g6s1PP_zM!d`r078K03jBVPRl@RK$4hn+%D(> zfYL&Lv8*DQj+j%B9D%cBnGpq@qEOVKV`&+b0Y*>^7Mu)Yk$^E^4Kl2f7if-SfGjN( zm{As35n-pxJWwl|J`t8HXc~xs$TNmQUN9ivFub6vyrkeokRl3{$Iv(yc)(y`ku4L-p8d-})W1u?~(|uWJjsX;ZiwbxX3#cG#!00kh0SqJq)Cb7Zpefi7 zK;N(u=rt<>NF*4DGBglvR$w#$zf8U%$O5j{qDmrQ4LFNoL{&93hGhhxW0}!ph1LxX zKdBsIrzH}qMSxy1bQv%QK98qBu%J(1mu=BB-H-%cQ$Tlg4KApH24wb$>;Nn<6AdwgYHEzEijp8=hcXn&fPvL#VLt`L1nkL*iUPf$a)wL;$8tQYaL^6V zprF2-2yhp5i3cL54c@|o21BVpwIQh*?ERrOx=bn14+j3Ta}i|^FwcrSFEfmxVx0im z_|3fr2n>hK@$@4!@C5u&v-f^!K4aUetuuLlmJbF4j|DfPWC8rg&d z#4}Dt3@{pOxCEBXc|Yz?%*SBtgbcHJfvwYc@?TzIB*p@D2$Hq^iD-%mTn15dpyPOpLiWa$t2a zW?UTbIKg-VG2dX2&F9TwU^HldRq`C#Yb>uDzCq7K@(UR>(6u<#KEYf9Oi9+{Yg#aK zDKQWstC*ufcAc3tWgWT#vqu6+0NNvt7MdCt0PYf50b`K_EG!wpENlWg6h{Tfa00pF6RxmR(2axtEPy$VUX z3&0bwU2%3`X~+r6Yl8XLnTWLT9V@`IyJQr9gC#-n216d>Jm>4{Tr3a?SxOAP!#*Qk82lhnJ#&JX z)Li;z@XRC`sZpS}!n?uhJ0>DeA|>LR)H$7jLA88|)rTa2rFLG7iGhm_f0*E$9W8Z& zv+vVjVDcJ{2C``O7CwOPkKEv(JK$635OcqQTLdRAe9h5OhEpqlPr~%|B{B>ER6wi0 zuW$qhj?mS@asV-oIL*w(IJp9P)!E?Y$i9T{Fd~;qxxO-w{LZs6zG;X}?FqBHGd!L4 z!{iP!foUjwUGFnXY^E^vC(e=I1h51ay{MTHVaiLi0(wqJ6Z`q{HyZby#yG$E)M0rvR zKlBV3IIq-;HIv)7`oX8vt|xrY%@^q}Z|mH;`s_|%lPURgkFkq+&%DX^;x}zlp>ZFI z)XwKOytmKlQSy%){XFA7mCk!<%*%#X_MCe+`S#V}`^(&E4~5YStd5S_c+i*L9CiZTpa`<*10k#VQ0&{dMt)g!(nl zb~w21&Zouo)fcVH706wx;p-QP#xq-!M#hwm0{96aoB`i zZdC{7&NW$?Kgjwy@;A$N0t}AkVbfdD<&t~zfIDVile`?vGJRu!klzLO^K*Qs@3K&BcWCCZzNHVf66d#UGop<5 zj1_7F&lL+!c>U(lBc?!?U48QXvdgQ>9-r*L9=%#Fe_xf#A1?38+}pQfhZ?_hznmj` zk@#UNBTKBlu(V&B3CE8dx?d;D`o6Y%i)sw(5ZCcFTRf*lDK)UcINm+aqfK3lkIC+< zREuwQ#h-B-)$@ryUJBS=K94?anOpT7H7b_dlYivgpH}S&m>DC-^pA^*{Po@T=S!P3 z*cG?je;AQ-nG(0a`@_Vy1>S7iU3bx>*3=K5a|KNvoG;*Gc;SeTFV5OGW*g7kf6%n< zk}D@X_wf^359w#0(`tjy)!0V8Zw(w1^X1ojZ#}ZjEfBZs>7KDSCwafk-7vDZ<@xRD zw#GRhPL_Y2(JC-R58iP><^Dtp%lj^jkad=R={J&i*(}Y%;jZ>Q0}g-6@uw^XUBB zf7^lz7OXY*RK2`?s%PyRu_z)T^UB}5p4u3hv)wGueo3CToaUJ<1` zu9-f)$3u_1)suVQ7+hfDfGPa778MfXTCD6e`j;|W+kCJk^{(K)u4mr0e^h@LHoncz zdZTBHE4G=~?k`XOc|OA$teVoUOtmGaf6H%qe@dQqt(C{#(R+mzgX|Mm{AF2uf70en zDXWB=vBO7}yK>~*{mAGNqiRk0@=L@it6%K{i%WauxiK*%amC24#r{0IVB5|cEt}N% zMeba*Q31>R#bG_}yEUm8zDQY8w_ooTvzQ5&e6!Z?)8s>s&sn+sUC#vlbg9a%e_ZzB zw>zBcADy|y`yclOoC)9Oe&*HHN;Quax<0&Irl{6Sf_G;9cr7t-z>n|Ll$KvQ4f}NH z@booP7L@F{=5=Oed364nem(fD?mzaMuySRi&XLR3Mp_$$#jgt-^<;Lfg`F>T9<{IX z`uT4z&JKDypA)A3JkLX(7@d4Mf3V;E2{(^a+1WbJB+Ht*m*Or=o4%olwmaLL9SbZ^ zA6Oq1@>u`2+@%h~H@29(`c1A^4N5i%cpn*bv1gruQyRDK8_;O{**k+g?;Y+^klon* zeYeP2YiH&>kB`=Sf^>yFZ7(N1MAlA z9`W>PA1^Oeg+I>2m*=T%Ix~ z^io}C@gip(&v`k1|HGR9f3W8Nb*zc#4{+_4e;=RUY*=_PK{Y#+oco`_nc1NKS!cmo zypUo79HDsyc$q%}{vAI2{~6BIbo7<+2r#cWY1PS^W(_i!W>=ndu%EO3<~wILU0D0S zjkDC?0@CY3DlPvnqar6TNElwOt>EPq;CzjjywXza6eUx@COGf;fB)We1f*rr5!ePb zqcW0H89FpQjwX1S?dp1Xx%_p23XcD6Xx0IK*EI*Ytj#NJk!Mr-FHuAP+P!*U zuF5q>jl!l`)Y36ms~WYPz-l+)hkZ%+Hs5_Q{L+=!b$i>ye|Gs=?DF_Lir|K{-|3%K zE0WqG-LD*0pQlxu3~2M!#`Uvc=G*Xb%YxB0LpJ~iCBhg__g{oL13bVZ`0r5$zLK@6d@k+u{n z-j+VUV`I1Ce<#%LC0_VBl3l4;qjqD*i4ShxKd{JUD>GqOw_oT-+d@6{8!2USwy(7E z{^})N?=9Q1qWI&CnaXZ^z2syZ&Y0Gd^0bH1opSwJw2&{nyQPi}TAF3?OZmSJ|;l-$j? zPHxyff1z<;YHIs@YuS-^&9arK-pfH9=Uz{_eL4A9z|-@~4qaJ4wN05Zea36R z^p?k5S!MTyf=x5<@#?G9Mi>6j^}luNVYj`hBm(C%eZJt4Soy-)A7{7$4xXpy(Kr1= z8&wLQdbPh$d&t;czV&DKsCV_77A8~W_nGcTf0W&P@P6Y0rEZ^0JpFOs#QTrRmdJbg zc&5O`V^SQ5bI|@zXGbi}yYLB1wO+3L}^_KdEXQ!U=-VQH@Ylbc=4 z%6-cP-~sNBotMz2<`=f`%|*vQl#3i_uhD+n%*S3ON6a47JE}w8e#+Wv1A}YE?U^<- ze?1EM3OOS0Ji6dm(b*r%m*i?6zqLJncA(dq84Jc+8r1FGu6+O7IXm`r%RE26-jM#@ z=NNk1@=r^Ig$I|HP8m2p>hpnx-&dVb@t$4S9{WHhy2poCj z8z|Xh%Yd>)KYb?3{vlWce>rki_RaZ}rcaM{=vaT$m96xM zxUKot*81hGo4ewc)%w29#kh@!swSUawU91}8E-GE6#nCc{5=k5Z8vbOi(WV&a_{t}mJJ7tp4hEtixZtn zdX`98|7h&p`ui47oB#Mw7gx>C+&QFk)J;U_0%nMs6DN!w)r{L;eQUyx6)HQ8#@MNs zR_Z5Sf1eoAFjv(<_51XJFK&5tf2|)r{YsWD@7eNsP>XUuT=!)6o&!s!&ExLQjhQ#k zdVlo848@sn^!T>hXMT6J1Z#$ksoj5+m;L6D_=x|MN#!;N-aiu7*%~j2J8=8CF#O`> zA_vz`JJe!Zjb20AZQWgOOqtr}UjI+p_j6ZwBS)XP>0;?Zas=(DF}Ld~f4M{0rcskl zR`_axrMFMGnNa0nsUC+amiFFw>r}~Q-Anv1RQ-k>-+sC7#-3}FZj1w5ve> z$_=Kr8G=fMSxarRH2G=$;auUVm5T@KV;dY1mfu+|7K<$DnpE!(3*U9C$bM*g)Z7|` zzpK2k(S&)MD#hj8e{$S?RnrCaLypgy7k&Tdk7N3D`@6L7_VaC$^5y;EN#n+>JZ|}6 z;$`cLfo20@*g>_)vtNv%jv?s z7dB~=FVpe(`vooxAKzd@CjEF4w)wt0Prn6^lv?G|gZ!^7f0vXk=}B&-_BW8fyCbD) zblFu^?(A+gGV41jDUoh7xH7b5;q<_0GkhKaMlYE91rS$Ehbz_krI-3Hu??%1z-1TT zF~zg5t+uhIP`h+5UX5rtsA~Tuhtj_o8c~1eFEw&k8-b~f-1fMk->T^aN`I$#wV^%HSMM8`jFtAzygs#hm0&pY#-c6XpUG7)37+lCLeq3(^ZQ+?q+T#M=-WM89pW zfB?;GE3h~w(-Cixp%qc-2wX^8^Crb1#{>5v#z2Fh9ww1GRKhee02{N>%yt2YJ9&#J z0Z|Gf6h^92xFD1?6;uL^7%^HRv4sxXVD2@Lv@B?A9LxhUWC|&fA@q27Cwe>qZI|w} z=?EIre}$RAa<#os1qO{K4;dj+X|@VFL8tixvjlTst(+P{Y!=aBte@%V2yub_AOcKP zMG(MrQ2JErG%IKNM`9mMK&sFsra?f~z(u<>KAJ~>2(~&8Dr(?%1M`VW>1ajhLkoH= zlyY= zn#H^St3VzyTI>y1Rvl9aYBM&lqG$23F9ypF!?{ z^Z_fXC^%;NH1IU$21UlpPLF^jXIe?KuUQQnP9jVRE7$N|&9QQ#%gC5ImJD#lqUJyW zA#uds0~=6;4KM@?Q79hh&ouFbNC+Saf2sgZ;Jh)HrZvSDL3j|Ogn4x2nP`{baFcqf zc9{s_otprXvLnoMxfaD`NdP0RQhvQBxAapM6yf1#N)|C;7(ZlC}UymYA_-Scbq78$l){rV-pY_X>o=kir~* zCYr=ydr}TSyR@gP8BX=W0%8))uxUJSc+>`P$V!2XcakhfFO17f3QZ%Vsy5LYS(h8=6k-#m8bx<%mHE>=-;=rO~+`J5( zOF1Ya&E4w6e-ryHfRL)tf7OPJL}t>G86E+i1gs5A4ll`k$nFHEC?x_E03Db>s7J*J zIWzN6aGHXyNQGG%4{G2tOdUx|O7aK~^htt>BzYu-!#fW+g@}1-XwB!*U3AYFGd?ZYn#0T;?Mqn(AorFQ)SRQ;hoDGKwJpuzg;2LjU z!Eo>B5#9$7M3x>43xpdz;89p3i4A;8QW7*IzVpHT zN?=lD%qmcT2P82v$s-olmgGa03lsoE4gM-ADVGn9h)E4M7YdPicwicn_}H5sKBPpL zQ@oZG7z?ZQfU__Pq&F!^{^$7T_~-cN_~-cN_~-cN_~-cNfB5J4zwT&Xd~}te&%_}c z;q}|>a4?cDt|IkBZr6uTEY=90TN9Q;{C!sh86y;b4C~Vn zPd6cF^hKS?e{XA<-uNr{V&T2+d4`iyK8^A&*<}t5@5nBP1Pgz6<;gLG_V&sb(st!R z+jHej7XxHBI-V>jHs}i;D zSlNeFd@+v0;lZ;`uWEjM?VW~;9#&f6e(jH)4KqWuf0@iD7pG6%=Gv#OT-=eleKje# z?f7mJE{}Y_btazQQu5UD?9W=SZd|eNn1TMaG1O7D3s#%DX8>H>`(^D*_|@+6dq3f$ zbSpaB$-oi*hrN3Y4Grt=z~Kc^hqzQA0|jh8j^ets_OS~c>z)^Fmy*%xa%7TIsY zTKmrl4;I}WB&g*#jVUOe83s{K@%F5YpQt0Be;!Qkdg5_+#lsEUhLdIX1uj3+pnvkP zY(Krm3uHB+Lp~|OJA5_iuOYkPSNSI+ZrrVA!+cIZa!kqnThE^6Y_+00JXurWlli8& zLV_qd|MShl2nCOEA3HAV4=)}c|GiFv->A!T-jx7+C9Wy9enGCQ<&NdXyYoFVEq#U@oWDMYUuxxh;g3w)X1%Bfzuy_fiWa!Ee8#z% zT#rKWotDmPjw=jr_+i9h-)C(-_V10_e>*4*G;6kyUkkd=lrtWUCgpyfwboDTHvSTZ zZ&z7!ZOU5Hea|`D#!3ap&u`DIC_ie#1ia!KKp$#J;=i~y^V*fskH44$7@ReN45^`w9#B>h-H^*UU#x&7ua^tx=|(Qm!sD9&jy-T?Ql^deGf1W_^xf@lCek zJ#C*BpK+m{x3`&z-%it_4u63^b9wzRIRJid?^&_E z{<(Lqtbwf;%VOK0CaR>j+^eaz839m9p`Upxc=gBS<@+l(zPqi-sggzRRjwQu z+qqtw=dTU}ZC-lT^x27qe__aO^KpRQm8@8t)C!lcet?jP-1GT`25~RiU@9+6e>rEP zww9be_loq@rQWrxRx5ekxFcsk8d8C}25?Lbr zsraRPsjRQ3{QAzVsTbY0&TosK91IRA^84jBuZv$@vuf14%h^X!m#z1UUx*(bpZ&)o zGurxJ@|jU;Qpmhwi{1~b&)1u~e(9TD^K0c?95=gGy=TRLf9}0{=lYuue%yZY(IC1| zd@aws!ILTvX}ToW>O+ZzY+avS^EjNGcXmRrmdy%>KI2;+Saimtf0cVXu4Ow=s7YS> z=Qh5tHhuhj_*mD0V_wB;OULJ0U%dSGh^p^PEi5^#VBKqhmh(k#{8cG*z^)667I@D{ zq3ZWK+dARsf9Pu0P8KM}efq1!wT4TNT-ld--I6;EIL3e5tAW$HMvr~gtw#IA^WMKF z?pz)mIqUdoV;|MI)3QJE?`!*8!F82aop};=d3-mq!H$MiJJMTooc~aLMdDeeU+}Cc z#qKrRkyxv2%AUlcU8d9>)vLvH@8>CO=d8t$$>X)BBvt9+;>?M<4gK!Z8IlZ#f)HU0#RQzep@P%*P8gy&I&be?b zZ(FJVfBwsV_RCRoMu)8vU$^S^>t(4w zWrN$o^48v&_w87;_iaGGAM>qwdUIugyQ?~0e{be>Y+>IQ%bS-BiFjRW;P$TJFVB0; zx>R9f$q`E~-|2OH)`2y3fBtyztnnnYRUyAmUP=4Q6s+?5C~xmq_mtmi}iU&>`wvf>Q>SNavz6`bvrBtSDff6jx8dcS+5Xo3^{4*Q_wD-!DP!p`+s2R0I%h|z z(ot(@ss7Owr@biuanp)K6uWVZQQc8?zZzC~h(#hyLu z9-jW(=XmVE6yL(R>YTgPX5!UWf6p8Iuwi%UbtT_Vq}~->+&tp^kV1nzfCuac`kd*# zC8lAC)61*3Ewe3m(+-iZnony{Avr16Z*`}q^jMy$@X)q{4&00ARVik{y0D)oRUFc2 zT|!`iSdUY~SM?UMZs@(J+@6`0R#ne>#65rAOw(&sp4%v_`NA3%*2XNzfAP2(SD@zO zQ3+KN)~(GqefY%t^)Ec0k~rkYS&1kOVQ=M&%Mr7aC6&EB_jPgFR9-kvRt7Xt17;IHvRgRJArSiT$(Yv-K3iz zItE$&+jW{df0H$+^C-5He_N-X{irK*r??gBIpto$fRaPAW$(PQ?|nFRcxT+G_4SWu ziwvrnbFp;o;%Ki$eXaMZwrZr7f4J~V--O!D_qJHn_k7Av+5C^vf5nEEx!kPF;DcTA zyzaSgOAPlU0hai`RPF&vdrw4FnRRdu@{e= z_qQZg8QuE+)$5&We>OdQDWubukqwJB5%Qn0-mSktjQOi-?qf>fJDLGER%}lQIDD&2;9&PjlNWN4bLl;FK%YUgYj^KFz4PK0 z!#cGe5Z$$Xe|GoyJ5APwzy7WLs+y%Er@X#%?n0k=cXkHOcvP&@Z|jP^sudj@UQB*6 zZDfPD{%!RBkx_drqx+sYbfiFyoZ0qF9(m<`mBIt9nTp@AN|WP-rh|8FvYe{+@blgA zo2&O3@F{E3y6dl(MqK{$cZQYHwQMo;aw;Z#uA%u~(7s=N)j#80aKF2H@^@#{%f2iTM9T<{q4+IQxITAVU? zZ29 zZ5s2jf9*(rF=lC>g3E&JCAIPo%C&f3qolO9uB-(fy;AVT@E+qH6d5JN58ifR_>B8) z=l3ss=u=3AGe1|q=sS0m@4k-Dzq}N^Do(VxeHa?+UF~qA=gt01`fc$2$exh3h%cWnNMxWf9L*_XO)Ll=1y%=s?o_Sd0*D|8Z@=` zieB;M79DI;de%~3d)eceB8~o&3SFE(YG;p*OP0-B{<7EBqHWYsGn0zlZWfW>Z_RH% zhsFntF4ynuy%~o)8K?L=b4#|}=D+qqLYJ|{7nMBFy6yQVBLlsASots^dQ@;@9~@vNfx)zsciyV<)%B(W#X0tXs3X zjqq7DDCkf%wc&$-?_ZBUU2yL%@#gtEC3g5P>QMj4iz0`sKd-$ZZtcpbBPZG=RAI7s z9KCjV_RNR$ft1ESJ-2$i?E9{3zfZ-Je`Y6)4Xjc7U4SpYO$9IQ5`Im*$V2bt$&jp2t2tTfJX;pUyg|!B2hjZmWB0zPM)avPUd;E>s^k zYiCHF#`Bj9Q$7?G&gIQNxpzxC{Lt@hcNV>6jLx0EQ_kFvLY9aAIP7Er;m>W^e;Q87 z^uq6lGOK@}+^1}6wzk92=Peq+_Hfk2isy!v{j}%YA`% z)2K1`JhCj9oU;Aefqv^ZoUV1d@6-F-YinrFsc~1=mtK%1qJ8X(y(w3|^jp55^swtO zD}N11nz>opQG3x}CFf2FKOjF}{4!tji$4!)H2Tb=unsMAF5%XVYd62jfAnX&CpWE- z`QsB|W&P!4woNLy-F_=#w6OnPW2WZA#dqeMm4E4eZr~4L{K{;hB@cC+Hgx;RfMT8z zhw_%rQ{&|=zVYg__bz`q*YM@zY&pjKc|WH9@=P!MvnQ6{_O_3(aYCOvtv|gg9UFeL zdN0vAzxEB+Dx!ts2;5yw}=^ib%|D5ppe zjX>s-6(`16Mq*P%kakid$D-h{460I^nKnJNkml|LN|iPLSv{#VU#d z94Z;QXnJC@yOD^65?qrnN$HmjDW^2JM5iwVC%9Hd=Pk}cq(OzzQ!SK;8x$tP6tpBT z1~0&9GR>m16AAC~e*}CMig&J>m7S}mNP1LJq1d#ZPcMnAWPnG|WSO@B`e;_;c!5>` zXRJZtXT^d6GFVa3bluPmO;RX|rzlwz4N+4VQG+T7BCBdTr?3Xg0sAVDX+viOR@Qk# zHe`zcn+aJ~EtJMm_>ph;DV_oiP!Lp>;&g^LG>wuu5PDhVe|3r17@cJmU8Ge(U=4|q zVHjFbXn_GR@d~d)8{+pd;79u^h9YY;&vAmxh&-%HmLYD2)qzJT zScZW(43Xw}N)|X(7iG=R4A3H#g|_D;jnh?{<0V>>WD7;Bvcbz>ky&097|q}%nc)Nh zhE)Wx$g*fSf2Odo46kz*{J3X>(kV{jWL{QO?VD{cQ@TVM41V{A^j^6Hev-NBN9pbioh}gZq*tY#2EfW0hAtjhd%y5G_6 z3~jL}qJ^a>PN5~m!YHb4ktu^?6rK|){KQnvVE@YyXBahu7%I%aB@TvR(FH!O4|ox1 z#gH`t2tze^gAy4|GtBC%n#Ay`WO3nQ7u^SzqjgQ?SXN|pU=51`@1Pgb**;On&kz_$1(=1X6#g1(gYolp|gfXg18)4zsKiXKKc}0~JLDqEq#C~3~e^@A0Gb{pwaN{LOLRNqba1t*w>9;5v zV{x@wvS5)pN~C3r4uXN7NUU)g@n=vfBN+e68CfiXPQzNAe|4GHc!}d^{Cs>_g%^g# zvZ}7h5TMBO3@->egsd{MVTd9NdPYg23Q1GM{|J8)G_q)XZ!*myP>i6-2BV9-$XlTO ze*{nfSE#vBJ1!dOe$Ge z7++Iqo#x;rWu(yqC?U^6X=F)8A3%#Nl9J^=w{72JdWID!UUf-nCF_a+@Rl@2P*e)2(lAgEEdPsr zhK!4nki{5{QVdb~H!T*&TPQ{~6#QUuPy|LXD4I3Ye?7E=XGK;KHBA>(O#mne9Po%N z(IUlK3_~#f6+x8i9aVbdi%;3R00Ev?!YQXz|&q42l zC?ZHAj}o*Xoe?O*flLiV4Q)US7V-&)1`J#X)bIYa*!GH#$?BWf3Xk-M}CCv z20()h5iS6kL#;x02Z%>IgNR2Tf!YH_0E1@%pQqw+vcLDu0dHacF=ajmA| z0SV9`2y6{Bpb!9_2mN9|e{pyeBJeU;3P`y|APJTgzi9!?je#ryw}GMpOQ6d`0)oep zfbbq10|9UD2n+-~uo;Aj{ewP_eAc=O+YyO$9%%_6JOld&IS>1Xa30A!u9C)YO9ENc zfC8~1_!z1r;FbZ=!Y(Jq8v4{Uci6OG8o`^Q;gs+%_(z_6O@cW+fAA%i0QeGIA<7VP zKV%SZ!0Rvzp?Yj0v>0eFIHYC@Hy#4YAnsxe*myuqu-_F73|tR9huAa)75xQF4vRoi z#U_MBfN6##0&f7GWt13V9y*#O1s6^D0Cf!b0P>5y3rc}5n|II&!VZXt5D9r0L>4gw zZVFHZMj7TLjR))tf24%$%M)IO(P4EsBib5}K_nWi4Uh;S8X!c}8W2U$M36)g^cC{J zVhr>BvW-b)G1&pL@`85){or7E3p4S?~eM)5)A}OHx&?FJ=vtdmIsRk77Aqt zEEKMe2%x9I0hl$x zl0ZdN^f16De++(U2dJ=$%z_9ju;7TcBTx>0TU$44fq~e@L&m`@qeIf9dmJ3WN@_3sN{KJg^Z(Vt_s* zyn_x98x%|xsy3=Tm{d4R6Nw^I0(22u0$>Y-m-G+{ucL>63uFMQNF;=`Cv1K!X!QX6 zu=zps1Mtua^Jx4L55(j{(m*gJaB`+zvxHQ6!u&`um}CTui2jBcQCf#Q0L0N6z!82*od9SevM6#01sz5Jren}(z~Kf9svT|>u$B;-26hnm9r_8}8fqDZ%w)hW zg%h!>z_^HzU2quK(I~QLPBDqdSZHlg^1;h8(B24L4R<=AF0c|HGbBI_l7jmJls!lx z&;~RlkC+4bg@%NhqUU3A4(M;*^kvB7$a^#@e+T*mSSK1At_lDUIzxhO1&us~Uo?Y^ zD-x2SAq~j5Iwfx+*K0)`eKk%)7EIZ7We8a6sULi8N=0J_C zf0u&9g3eY#Qv#7QXi6BsIXW_QD#&2y=W+KzVUG}1!Cpt-ibM}rQRSi6p)49K64f2Z z8W#z>D+QDS`hkW~B^Bk>g7OOXmO;@~;WrQ<8gytrf%pX&$butbS6bi>8Kjy;dkGgD zDgi_W$ps>#5vPC);4>sLAZjrC5;1>3e^0@tlLsSDtUMVW{R?mi_+WCI6EZ|sG|&|g zURB2KME6IS2YtE$5(thQB1V&;kgW#<0v^mj+d}*;89)SrGziHNSDMOHV5~XdW+Yd% zmLgIHSR=5O3bB^>3fv>Q5@-X`IxJ0m0EfE;5nBh!3jQB@4;lcF0Kfo!B*q90zU_5pV|=3O)-RYQL!aL90`aIh#dL{Uu} z4)#7D$}=+1*(hd zMrVa7$0b8|SU{r|24!+|gdS>Tyq)Ghdx5*+Mk zR6O}D?2z+-z~Iz_G$s57h~ry8cd(;6=5-g22~Kx_kOv2+_qxFtd9WEsy2UvC3Ay5O zV0m#d=DYMxBb~|wgFAG2?rWcJ>bH3{)u#ssLw(Vwr;j{1qj?BUe<5n+xaz8RY4N7g z4ZbnuHQ2P}n7p>IY*ZVvc&BYj&4lS*T@0)v43-QA-Y+E>o5>|YbJ~<|dUD6ZCMr{c ziKhq-&R{q&1aypPLpq#WhDrX$F*+)rE~3=e=GzQ5EcL#WlyuLSd>HJoWf{u|zfC^G z`(3vTBz5WMPE8$Ye>XFAu15;ATe=h9g|~Sb+(mG@Nnr@$f>UjYc^`2U8Ni!%!NqEj zlSER?>)&F0=+aci$9qV=$=&HZeENKU-N$KVz~6ss-GYPDni1}HWFRFimUNyvt#)Y( zCX@#^yOsb#=c@8!jiDPhd}{J3;P7GjG0f@cj2;#Wu)rDuw)Y84gJub{x*g zOr{J@BTUDVzdGb#eA=fGgIMk&tve{&P!jLW5){<`y_37Asp^BW_g!e=;1xcjRK%|m^j@jP}n zz6NWRF3PVUm%1Wit-iY&$GeQ~H~o@=Q*e8Obw~G`79)~bx+%f3Wk_GD7Ya5-oU|2Q zaKg;oCS**y^D*;Xnx#vho($^oRq2AAijwBAQ!gNde^QCkc`jW-zCA^Txy+wver+M% z(Kg#B+dqV3V>o}EJgz1_CKuElDz+CmbeQ~Yf< zM{1%Iqph|?yEVlc?QVt63rV!0$Mz0^J8f1I`RJdm?kP5JYoaaDJH%>>j`p@CTK!=n zYp_>He~NXbH#}@jv8M$4r-ZKqmT`;qINVIpNzbz%$J23?A^EaR5VP;!gv^5Ua zkZ5(~XWTJZy+a`6+tDzvidl-bCa$!7g4pbdf8Mrec+QsSZ}m?Mv0Fo|i6M!YJa?N7 z7Z(y|i%#>3_Bfl>8sZ;fTL}w*B-&HYc}GKe;29Xf1}nGu!&zH&N{ZDE3A0+g;76hz zY>W-&yAA7iG?g`ar8@+05bpI(Bv2#5ztOZE{6MHm3icw0l_5~IB#AY3<(Q^mm& ze-6U6+i_NZD=ZLFemmv1)oPEn`lphf4GstvW3xiY(a{7Qwh${(MhxB>?d`zU$`G3! z|8G4IdT*!@acm(_2rtO2zrTNqEgGy>wB6h0PikV84JJ>Fwjm}Gz1^+RA&FKz-~!B* zXbr)1z@HF+45@bNx2LwTf4@B>8fp*?34!Nr)-=h5VWP1xaUs!hR?L1h zKqCbTWcLPuK#t>VHgCX{^_C5hWwx=mEe`IoC8k8DnA(b;=H}BO|3-d5bZXn zQbWp9ZYR1!DY3FpgT%PRIDj-jf76EPZ32KYo8AWMV^0gCp-R93W-mPxdm3~>9lH3d@&Q^HeLk|S~}=3-@tm%9~0 zarX}~9baM`G<*uu6pRWui-X^QYjZjq$%WIrnHYlwdE4xWAE*r69R~yke@y{bz*9q< zY4+F-jOOis8z2Xa6PE}LM)Hn*IeD_(hVgr)q+p7m5fVvZ?3gzQCJx9Ax*QjVR6-C9 zZ~)^D#emZQa-;<4Ae%SrlYq~lb3WN&K8y;Xmy(EEjA$FK6$@1 z0Y=cO(IIwkE5gYegK)o%f3-_V^s)hMl16~bFuyhKcAPDQG@J|t0-l5w0ZqAM^n~*W zC%}#FeSnRu(J9C(-`eQ-+v2=ZAk{WAwN~s8B;>?kL@HTyQ^*pe#O{B~9uk6!PVt65 z!Qh}1OvbNbhlYg?j!y9=qXPz`0y_cQgb?gOKcu9@*;2f*KsNFqQVfiRdk!0B zAle#&dk!#`?lvMuag(iypP-W7-ih9EcIXfQeIj-;jsQIw{r|9YEl^UFSGv2ayXVo< zJ!oVD!y{^1lcUC9f3l3LS-}{CMKtDQrbK*XBkFn-w;7Y*s!`8Tf~EyG&JyqyA7{*z zJw3{bgBC$x!FFaB7g6znO4fvm(%txHprz{uvIPv5;X~TG*mvoL#f2f7*)H0pA2~$& zTW@Fuln^{{e{Ki5;D7&P6el$~mNY*`mZAAzkmwHuT?~`7j(U24n5C8AqlqG$w+pH7 zf<)*Y8XBN&xTZ8s)IXHHd1M|*r_rZiX?kh@77P=iPMbpM7BmS32uh?$DIhKyrdEQ< zwt;aTGvZyQP3knzO!aW(Oc&z_Ev1%do!3P*Z0(|Jh!+D2_r>=!GQLb?R-n>i)RA&F#FIhj*acNZE)}Sanp@~;&MkV2q#hZj@+_vV?8UD5>}_q6 zYC#E7f9^<%pukN4@BsiHQql!QiS4_n>uH~UUJAtO9pAQ{S{D#VA@`Cd8fxhuKc2vt z+Mr1sl5yy2Eetg?9gZWztt})A*%JaUYSn=uf&%I{!tW*kCQ92102@GlhVJcWY-(dC zXzsxPUVouXREE}hbT&k>qGxFeTWJHTSpPs0f0OTLTmCAIN!ita!b|jiC2*~!lzt_1 z+tJXiwJg$;lq^ywaKDkflJQA4i2F6Q)BrB^aG$1Fb;0*E6f3_xB|YVUuB->!LOGzY z4MYq~$8nEIStHdq3Y8%Nn1OVLwrQ*(ZckFWBB-54FBusvigd9Qt;ZY$F#|D28-Wy2 zf8dW+;|))9TilhNG~*fOFH|`0sZbGd?5y{<1VgDV)zfJ1M5d+=Fkgerko6?26D0F1 zod)YE2@U8_+tBDj4k{Q?Hj&>96pF?`UMs?v6@Q9*yJ@Hq?qk{;Nh7A~NySA{jcI^B zK?J#{uS$H-0aYOh;>M^`==;Nq@0*Iz~J&~!_AY=;SOyQj*g88q@ z2rXntSl6%?O{5jjCF){Ido)BjY^wMG%S*wZ2xJcV(9amaN-D_{2@Gg}lNt|Af0B`h zbVCxzLwHHgMW8jILYYCd;4Y#BI6R83kvoZ}D(w?oEa4Mnx+Vhfh}38buC$1N4gal< zKg}7mn~M>IIC0S=l)gw~oTWPt4N6xF=K<_!z-Y{u)+}=q08c=$ztk2&cC!@A^xJOA*=74L01 z?VUyX><1>Fy66()hX>6(^!6)vUi62DUW@z@_2Mr z`x`wIUtToxswp>5c>U#tR~*v#!GYh{y#4d*F8T1S503clyB{8S!P0??pS|v+Bf!cmGev z)J+E*J@vR_4jX@?bK{EHU4NH6ct}^gc+1vrD%PxAnva4>|bTYmPc+ z`+0{R{FwA5KJ+$dd=cT6QpR!)fopt`l`@|MHrJH?Da9$gy{9``w3|FSw_8^&Kz0FmAsC-dj2K__t4AcHM8jxN`DE z|1f{^!`J8TzW=thkAK{L;Uo7&PQI*W^Un^vaQ=>0FT1d`w6fm5>$q*Tubg||&@Stb zS6{#IGv{5gbJt5VmNm^d?7?fUIO&I*_wPTbJNfrZ|KoE{y|?4k^P^8!j>;TIzx>AgljhF-&wpD>zrOPO#g>^3Nqzdl zBTnl6?8Gy6K62~Jy(g@4e*C$g&fU1?%xMe%`JsP2rugPn^AGv`2Mb<(=|?k;yZp_g zW2wt_O#8*ZeK+#J3*Wi#@)yn4$LGvF>#c3&Fw2(9WiU=zsx+EXDVO(Fb*TK2*|Ilh zUJI40@>hoCrhoEC#V&^lsVw@M+~s$Qk@{=*)Go|kE{A1cwMZ_zM?ieY1p$b?jxPXX z7#cR=6D?q!E_gz|96o&Gbh6uNEsrt+=rZLE)<+74!RMu))!~MG_B=b`?Og@!^NPk`)N$Q7%%kCN;ZjY+7%4M}r z5F?piWS{b=?;@_}B>dI4y%c4kRv~}Mld?>E)8S_U&-m1%zp~7CI`hx;p-N5XTqsCbm)$SpO&uJOwZ{$ z{h_NmANb5#gec^zL!=8{rc2n#a&p+1XWY`AEIpa!cM-Z%p*6%Xcv#Ol4jJTk5jtgb z#4cn4&_f>ZHV%M?zB}whmTp&Q566LW)fr@XCG@cHCy2>$D8ssr$%Q7PKxoZOGJg)5 zv@Fg(>=EkG9sn@9PpD#%Cdn?LXW=KU$?7`Pw1vWR%t%rWE?N%tGF`~&IrpuH02JHs zVCg!mlANk$ncy82s1WHP!U9)=q&fn2vO0q<=?l(3Mp0&H278b>sg z6S{_X7Tw~-XhlcQl2r@lSofo(kjE$;IF&w0^ym)3dZmIs)Gde}as}8%{^W$qayoc3 zLavboLLFV_3OEjR1zt#)+gLE733YOdn?SEDvA}I8Ffs(tzUujVV&WNlbq9Gx!JCIE|BAP5WKVD=gf{Mel;1JSLV(DuIHR?!L$<3ItEMLSsX)!|{zacekRxZJqT z4n`=OR%Ir@Nv@M5(Lv_AvLh{%b5BXoQG@_+`at@qH>lfkm_mysnk5pATWAS^oMS$m@_*ZMAcZ9z4x})@ zSYRVviCw@UQ9r$USP}|Qw;~`$=KvenLx zOu0=RX;a+H<#On*nSXGMl_|e?Ky&E~L=zAf&^-$`5F!H$rkKDOHvuOYLyBbyQJEfM zVro&&3l0>L+MkS2A$6!!8Yj!bsB`zxPY2*chNy9}V6qBj&JvApNh6aRFoeXB-etlB;|SpLG*GiR6)Ok4chrDTQmibOjdv*U=*cbO!3rmno26!n zBdwIn0c+?R^*!N0MT(9W2wmQAblrdBpAI?V}i zvG1?{s6wp0et(i)Q}%3~aWPD}e~!0*DF@mHpJB>XH<8ADxfw&%zBDKYka6TYBi``T z^uG6GUmr?MfAj}g!1|}6ruXnnpzxo-p=8n?R=E;jpKq^pyE=ombU}FdO6uY81@(%s z&t2N{;nMoYyDO9UpJzd!dLQ}of8Y?Q)5q%eW8K%2I)5MMuKzC&y$Toh#ldQQ-<$ob zI$WXqN&E4M9`QuHE*h!DSNxKX+JR*sm(;|8>5LtCJ{>~Rc5>GZZ z;5XhyYZCb7g<7h%kz8m@*Trd7q%K)so2W_Q|Fh)BGO@;ZeO)FVk7)7`E~e}2qYbG@ zDwarTY5bq6saT{QzeY70t4nC{XeJ(OjMYRU$$w-j7LPS#8e_Go8geR0J0&vFXu7^8 z9Uq>;*r&&*$EU}q$6wN6hfZt;D#JF_1=F-mzT_^G6YqXZv+2Y%1mI+?GU6iM98=T8 z2HExRnWl#243eg2nM{~L$h2YE*1{eewqgqRf?8|ahH2aEP{0S-&E2`jW(x5)@D>^> z1AoIjF#V0q9x_u!H~bXc=n1&Ohp-M2gUx~191Fbkh`}}uQ<7+!J+`fJTErQ{9=5l} zC-Q@q+IFBGeRR6sR+rQb)0QN#N483qyT-KTr^cT>wi~g!Knr`qDVGYEfi1oI8nz_e z5Lz8$OBI8UE91&{FXft_WK&&x0->2+CV!1UC)k?d2WWc+m4jxe;E)b6x1MgiwrcBu zO_gl#dvF+3rg{v_K&U0y8)XP-*3j1yodzZ3=7XwYARnO-8+NEl_zCj%X6u@ta+{Qv zp^C)oO=FImQWY5{ksLC_8O^tc#x=2qvMAn3(O6)4^+1Zb9W;qoH47vb2FnQK27g0%skQ-t6rIRc zGf)aI@g}gsu>Dfdk{%?_l;ju~5Pw@xcLWt_uwxvF={l`i+cbQWbKI=sG!uhDH_S1H zhT6JmH4GGu96>hZQ5aLo-yFWPt+_>WIRKDmDpxeS#})&IO0$=H&GZ~LLRR4nnljia zG1btV5VC1{wNRfi1xmgrJ1q*Z82W6-vhC6nGR(DUJBcqQeka%J$9zHnbiVdvN$`uGpUWVZ2* zoi)||+!PEJ)WmbV#|+!2V$%?V25;CSZku*6v4)8-&@k$0lW|aD_e!Af6LVyE<89hw zC`NNw1p^_gn0*MdJ=%x~QGfbLSGxf5aM(hFrh5de!T|%rEh8k{E#ca#Vm%D7Zbt<> zg9$*IjKR}%xlyyj{T7RPuVkm8N0h`xb)f-q-7pZRf?HW=DlUMpR1VQ< zbT-E1!OFy>dC;PiXn)%#h?;{oASLV4hYR@%j=8xsK^dI-XvRtJ0VTJB~!(s7hk14Y)YM!)CcT%-4IwOA6$Lk2cKi; zcC{$NQ{FZz8PGHSbz2-Qx&$l>*=hEq{oVG^QR16nI%WeU6+>xc*r0ty-->NF0^h$P z+h)gj3wQFo<$sjKl8jy;DE>^hd;7R~Y*#bF=aoR3`2nx~lJk*uwOpsh@Jo%S;T)(| zner>6b}5FTW;aqw)`t~-yDLirFS^02#iAdD411AI;Am0#QQ_jCMke=SyW2KJJ}LUi zFG)*~fl|0V0?93TyZLo<2Wf;sE&AyYi;@B*W>nqy=YMEPLcy@93o~M}q`WAV@UW}? z+e41wUsK^N&9b`Xc2}wBf@zp+C7p0caZb^DNbT(RYl#z5VmFlIex&MWG1LKQ3V9Zm zi(ZnwXIO*L_W+o=z2X@j#Q-3u6}Ho7<{#rt|ge`HT=Heo8^Ct#bK6; z>QB4mMd%=q)DWr`DWZ4X%S8%zK&Mcz}4Z>vX?;iXc?+7)}ox9*OOtDh`<67#-t zMQ3MWw@ttIxY7OV;o+?xJ;Ig!I|_Z>ogE*8+kb`SA0@)>j?U+n7Ydzw@s4117glzR zsbT98*xq9vL!L=Qoffop>L19aG=g8`@uwuQR(?Z9Jki@DMsjK@& zE`Mof=iYMga%wnh#mes09jm|Cy``{nMaSxrWewN%-CNccI{wtaODq1Dy(^89qq@$u z?~8>DV>q(0Ie?)VXoHau1aJmf7=l@jECwhLYCv||5uyME6EI+5nk`^!24=7;vGEyF zplD(>%_buxT;0a>AdW4+V`IC7g_0;y>yZ5`_ z{q8LtotZyIrJGc9R{!raVb#b;o2YkbMpuliUOke$j;t76wPIp}Hwr$FWHKIfy7oY@ zex1K<#n{Nm1EV8Bmm(!?JcDSw+9%Nmve%V?%8>8d4*jA81gAj%>y+mZf&7#T zWn9MhNuxBYoHj~+^1|ZBVpf>YlV!O_I9Bk>ej5eL_`*Os^1#!Q;pZ{-{iKcb{b(IAyg<@2 zt8E3?G%uP-N|k-AQrvp_Z+}=hVXf8jYEVekC;|v&$TkvKXf@N zqc?@rc^~D#uYt3P`vtJYkE4~#Xeu0j6vr3k)K9t(-~%Nsc#N=@XZcaV_dmI{iR-dY zwG#3i<2)-Ww-;&O_Y0VB<+6_%77LN@*Kk@?UP_lMN7-u!vCOBh9e+ffv(9g6nXt1B zyRu(+yj<|H=PY#1iA=G+zqH)71;L!C?Mi#(Pc+`A&-$tHJ~2MF-S^AHcE3jC%#X?u zWh~)op@5b~JK{oEL+7}h7Sfag0OQWxPnwl-d7VFbcjFfdJ1^}1(lVjKKshICWxrOa zLEve98XjUcE8IOyguvKW+c5ypnq%-3WM1&8B!&F%M9Xn zi>wJ72SKJ5K~P3#a0a=AOF*t~G{y=OvPR>CW zH_6zHrNDA46RT2o#ir6OCS%_!6pjGrk{XGZR%GEx4yaaux)CqIUIb%h=6X`;8$>{p z`bKxDXDOH?4ulxG?=;>GhVF6ZH%pW_-9dO`&tn$Vslj+k`dK2t z3C)5wg@Gk$lSXb69-!9G=eQuXno39Jd`?pc-S7ZjZDJd<)+%eGVvzD(G=fTfP9oV% z`9xHZZi2m3?0eSBdq*$^Uoe*}m(rw8kxfhcSh@>V z%zs8lp$G^bX-y#upm!MEAbIPFxfi5KitZEd8k8JCUCFK6h;d1&3|bx+{fXtMj@<~I zHnAI_M4$~F?H=%Ite_o^#Mc@~*}OEw`$GWgprf>m6N(Ka{&rg?ja||WVPNXXgrrBN z(TJTU?2HvJ-MDReATm{kI(aU4ojyzYD|r(F*T;f)R-Dm zV`@x|sWCPFpNy(oX_A9J_#3%+rCD(u4?gf}h2E`H@Gd!uJcm3#?o@j!c-V9*&bX6| z{Mt>|alH75e2yn~D(QhsmaAlwveRaEu?P3AdQO#?oPOZrut_|-l3N8w1isOGo_|}7 z9Ixue%csNjIC8(@Id1&DN-%*Bj!bl>S0cKu7v&~;<4$k>JYKfqWR8*Ldtq0PIPW14 zZhwWkK(4q=H?z3nxC9+<8Ls9MbH?4o&c(##X0O9J&1Ns1=6dnnYQ>{y^u*7DH?Ff7 zz;~TyL@Wb4DwWLjiaXt$zK?c~G4*KQ++bSv2ZU#H` zf?RM&6o;I=nOxIFVvZv{(GYy3pFU0)2W0l#6kLoM;(LJft)c`@CV->vO=;CUDfDT;m|FX$lQ3hApdb z!F5t@(}ky#NP8aVjZ-9>`Q_bakHZnmyx4N$8X8Z0a*50MkxYZ+q3Fh$KSbcD^o&+0 zNR^7$ghX#PBN`=|1#Shn>VHI$(@Ulf(bzzMtH56jU}6y2AKY*c0QY*Tm8zTG%ECtA z^Uz4*=ymY^ zP!@c60F3=jJv*%(wBZ5%^ zJc$ln$017ORZ(iko8uKqcoHaJ0o5;VJQgsV+XK`|5T+_z^?Hby>+a2%q z901j=R%sm3BIFTDtOEqE^nzNzHyY6dC6p$R$QDhC;XW6(Ch0(;0mNKPbQU+jxtQL- zHZFz0b(_xf99au^1b+cYfb30>9I8hB4q*ryM}H9_!9aN+7QjB>yo-M_ z$04@8eTkdI(q5S8R^kE&mnt+#Vl!Zeai?b-(=SSqZE}?=nF}^X5rqZV1O?yeX-00f zG7t2Est42$3;^g?B>s@p4xuZ$-+Xo95Lu(D)g92lLmbxq?qR#)MoXetbA%Zebt!heh@XqKwTF*_4VUv|Kzz2_F* z``!6BEV$;jJr+H1Pqb-{7@W53-Y?F-9YntSpUNMIY0gR=0&}8oPU^q)l%l84-Hn`xb~1IjBUrRsowmX zSN)6s>yiDRzWS&=KK}P>rhj(zKQ5Uw?eQb8Uw{7K>=O!qap95Lrf+}e*kAs~Mr)h$ z(0pd(p_?9^{qa|)|NMtL-aCHrU5`C+*OHG8pZU%|zrOE++vgqCe966W+G9Jex%|jC zE?RKqVfU|{d*?r`S>Ccwd}+YkZR?IZ4}Iv-ho9z3gG=^&aFKlAk8gkRyutelUo^rC zw|~t4zi;+u4*6W;GFy_a>?t2) z_rCaO>DX`Xxc|-DrXREC=a*kJ{`zYV-+zAg*7Y;zKRvs&eeak4#=6Z_udFOA|Ngk+ zU-{v4w;uZ3x+R-0y-Z(!OzZvQuRL_yF^3+$-=>QeuD<_r=c;Fee|z#9gI{=g?P0s$ zbJob}X-oe4S6i;x`oi_k48Q%8eV;w)XSD~LU%7YD&5wO%+K$>i7yQfIeeQU`JAZkP zz4jjc(%{R#-hb^oo6lLZ-{x~Sy)|~2@_ps(zK>tIWYMO-KVi48U9oZA-F?;9wk@5p z(mwjMzr19=fARivUkqPbc6#wX!}#v(x#ym5HQz6mezEM`OJ}}x^>_BTN4oyUkGdN_ z{o%mKuTOsN;K9>Ax2*85$N$;KZ+~4^f9qGLShbrs&v|wDOTYWQb@cCl%X6^1F(H-6Utts5`6`O>Gv1qaB#IP+lU z$^CxASEhe(?Qro2yRA6slt4Y*ZuTs-*|5Kt?cTboV?+JvzvV{ zy!uS@-IN@KYD|r(F*T;fpK2I3H0`RzDV8Bxf+6v|%^RGc zOT5lmlFXZ&#Mqnx?*pu48-E7NC>*>(sD>!A8pDa4s;HV|n6k=oI-^;PVyOnFvL>%9 zoW!%7%FBWzTD&Q;x(R2tESREbs;nZ*vSh2eVOW}B!5fOq^RlEH9RBnwPLf2Gl{DKj zIh$n|MwTp*;bmDjEqJ9-bX(@YP6a*SVISRuGkAXyRmJ34T^9_)V1FdtQW;G!SyomU zm19NT)GUdy42IPeSO$LpB*Pi9$r>_a+q`6J62rn8;484SZoy3Jd%Y#0(?rZf1zeJsn61liUA1XYs_ zU1lXkQw3g=8J5*l#(!2!MuNuFU=)Rs07^qOMV=J}OS5!dmkgN`3_<0pv_3q<)1Wcse>i-9YJO_ z#nNK*8G&IsANvCa&waxEj&qiN1JcCd0<@U?Er3i>L_?H-X@6t^m9xnWLzP5E&}?J! z{WVc0e&-(fBkwO83cC|$OI1BuYn6H~A!I~bU`*cQbpnv>V%pY-L4Nzj%upHagsTCNK{tLoq?uEeC9B5 zs2ycPvlXD?&b;h4ly>u2TM#KyJfFk^Qd<|hiI9?pQDN2O*o;LedDze_8pOofnAEyz z^SZ&J$cvW20`d^O&cth*5f1{38ZTm{0GYgL+nHTENq;b~kuww5cu|s}!Wd%bGeYNu zVJJD1tS8tO;HaX>0^b3&ODMMcrs7*fNuSSwW#g<}mfx7+`q zmj0o=XIn#6>bw=gD26*}@4{A?gW;3fyD;2NR)1@?T44x(OT{qt1TK_{gC+P@48t)} zIN%r!2us_ymx{%3@CJNt-!N=LphZlChwy2fOU{Nnsw73>BPI?1&TW-=YUCSU2B%7YA{9m;w$AmRg<7cUww_VJ@JsN!acP z0e>s((mhQs0Tj66_ON@`6i!-B!byAwqbPsEFfXeu*aEi7v#Uh_nb-rjilqtgk+G{a zz0?Z3v1Aa5K~x`^QYy6u3AbK*Cas0y2Rl=s2(yXSDMBkR^&GLceZ=3nyTQ zTc=YA+glU+(^lRUk~egLQY%i7LHOzjLw^fXte7NFmz+S)V+x`rt%-2wJ89g6SQnd8 z{8LbH5OZT^qJeQLwpxR&lpfvWka(K*UzuZFOKvz=%+$O!)}p%-H+QYZw8AdOVCCs# zyB1+YoDoHea`p~VqS8W07>;3rpg0t1R!vl0#Akc4J+s0wgsnwM5vyrd=2}2fh<`lo z0$PN&Ax)TKdmiGB)J_ZrJU2$7LUR#!Ac2UZg%%EWE*-{+fQR&p0$FO2E;b}7&2wle z#4+E+CM2aEln7Vj`~o;B8adv@OwM(zl;7~R)7Kli2ZR_`D~7QkCoSwCxmawG>@Cr( z+TKbU!H@_*G0ZCq5p0@Rsq7d{3vc?!Gshh)(9 zl_un@a>x`lL#0vDD4Hk@Q4Sr18bXs4QkjZ`G;;j*zV8`_>Up2%dEe)KzyJUHeRsdi zeb{^Lwb#1VwXQv)VvyJTX=4SmMmJ4l`e@=uhjnT>j(UUSUu(`l`4EE%6vR&x5jcR? zKg-bp6mkGh(9+LU8TS+0@PC2wJHkP`&hI4TfFqh$Bm}>J1a1_puf-4V&+Lx|PyoE) z^NavqaLCUr$>*ZKN4juh2!7DI5-_Wv`G92#hc`V`|A**kKkkL@k3q{9bN|KJu4DKcx=8F*FqThb;}oqAnnhKX(B*T2Pnh zlo4G3*b0S{1d13ML4TtL_JcYEgA87WA`A21Ar6ER(Lu;(@S8dkQ^-hoV~9FSMCT1p z!2*lm7V!eSg~>qnH4MnVATSNlV-46KDo7CbiH3+jD5wFz;OgkUu&JR~ghE2mAYzG# z86>I@pI`_PI~-TyXaR#ng8$8MWW)up;2R1!$0B$T=?LcV@$`xw}>km7$Ke!z< zr-EWZ9{6Vs)EmQ5=|+};odL0cxWLCSzXGv*^rGxgioQ+73R*Wp!6=sr1U}XfP&^z5 zu#KAFa}7~|bOg@Wk+~*fQNkG7@h`no5D6^8SD?O$Lx12k99jfELDn)HlFld?`1>8T zX+Avo-wj8OLDmtVKNN9}KNn6b71Ydc$_^nQIM5Jk^HgN3Bhdr<@X*S`{s_z=Y)BN4 z1^gRI99}CFiwIZ*07N7Vsl-1MumTVH#e_w}Gi1|nh;K$*M3k3DzXls25)FqO3l%u{ z^R|A!HGh8V!4CBtI4U$0JJQN<01=WJt)B%=VmMdW(qFeW#H7I0Q6Wf0DI7bpl(78A z;pQ_$(}tmf??a3^lD7=e{+7yTW@ve2=tBke5FG(s3`J|`56(9Tf??JL2!x?V_Q}_g z0=H8I0YvP>s2~#G5Rqnv=>Aa%845yS_;dXZk$*mMC`0JKp#};*4Cy9xBVT^k*haJN zkQx%idH4<(M|G26pa2C!y%9c!LUu4T9Mu4RWwTH;&v3i|$V27?)PYDdLxBmtls6nj zGDJhcx_pyZv||c--p_0nH%ehEg9{jdKQ78HF3O`JF|s~U=gM7_mC=I?7!O8cm0>*? z@_%)M3kawS`~WU=FQCn#&xjo~NAMfC0Skj1f)83QE^rBS5)pn!eFBFA7laAm8<(Mz zfHJxQl3h5IIErVLAunR4YSWpp2d&uDd|)S4LM+=5r|Df9Nct z?3G7+hl|5ZxLk*!gdGrs4S*cFbzNB>e1E;J%s(rn4wokph-oBbXr|yOG{fjZ;1jC$ z;UZ9-L(oEf0;ehqdK(Zf$UK-;5$FP>lW(F(0S%N7_g@$<7Z)_C%HNQ5T<5nBWo0Nr zl!x@NGFmapP}%a+=7N;CGDH@P4F5t?2et}yD7@$A83_^CI4m0Fk;SKsFefw!RDXpF zbQ$F>7i9rZU0l%MAm&k?h!cS(;o@*57<)dN5WfVT5QhsAL<0^|c0n?U6lX#VbQM0&DT*G=M;WP#>)?YHiZ3Lmd|U#2 zA++Ig9qluG)s9p!e<#RLWe62zBERHNOknvX573HAAU-0|d=ilz!wdj*?*g2RL#q=; zoL`$RaeOp@IjFNp;lMm94@Cp5WWKUe9)1tI9YPn%%EJYKh#i+9$IyJkfPW)qP-%bw zKw6MVWf!nBY9r!`ya~HoT4K@sRHC5j%QJ=B) z?kj(1yTW}k@&~61_w9Z7%zxpn4C&^KR?`VRb5lhvvoH5f`(Sx1dg84U^SVPATBXwJ9XZU~Bw_G-4|eg5fZl5wPd~!65{(V#tRCntwa~W=1xtA(I*iK#+3P zfN&TctO!9Y4Ega841jLISX4#~zl*5k_fWxBDxniV1rJBFlCM$uB!FCq3LP?!u&74F zP_BO_SafkgBZHHXc2zl|$|EY53!VfOfJJZNA6Opzk3-S<4Z21nNJ9f9rlC>%KZ4#> zISeYeMHh|Xf`5Rf1ej|Qj6+EdmBkuANoDYfIvo4T5itkvG=}fNUxb$Vj64i1jo}TU zMj+qwU)F4R*4D6I@$0U8jc=bT94kL0W-{hN+x}>i2&d%WIc0Na z1ascal7Bb9voI{z)FJ7v?D8YZ^FH-{i7w7sH*4>ifHVv?Ky1nLX)AZ_Yn~&!VdH|v zaR1QFwz-!E&iKvPY%^f|L4wtlpS&Y$hoq1`5APXSWu=f}`$8$-e6OQ>@TaUP<(2nz z9XwmMI)CdFz0|Ba zjeq5L23Iv}NuNml-mx)m@2M1Zi;hiQCUT~FpRAV|rRN&Ii8!DYY9H|VHu=_`i5WBR z^->mD`nibgIY_8||F%F^(!c^c<*^Pb1aMCGW#ftIosKiFF`^UL4ijJNHjsekt* zZdk+aJC={1V*jRcVaBSvT^1$_J02%G8ZVKZo^;j9ewW?YMVFaf8a5X+ml__4j(=~y za_@`xwX#yWdatK33(8w2$gH`!vG$^^Q;x{(9kWCh-8$a-Y>UK28&O`i*aoAM^9(%p z)V*;F8Vq_JPH)wjqY*e&?A;QzynphTxl{5l-dJ0(pP({+r=oFVHI*qkr^aqx`O~9o zOWz$`x=JLVOl16vb+T43Hz8>#Yku`8*cX?Y$ zlcSB>s;ciQvAMAyQaWeOiRe{#%__Q3I&sCc) zyLcs^Ym;~O5Lu&;D?KoG+Z7E-q4a>ns~i{Mp2(2yjQCsbM!vfbSSA&HR*7wSUca%_0-EnfmjL1C%?Kl>0f{v|X?3S$5i8>SOBr1(XSU z-n=CakSAfjRjl=1lX}*^zAdW;_H*XPkkQz*)-yl z+q+p$AKq90aW7@Zc1DNlio1p-9y`9BskID}%Eve~y<2hWXlda6(|`EVJ{nMt}=WpL0!7q8d4JVU0A?fiQo;0p;Nv_}C*jTfm zzUXllY(wo9VpZ~2!;q(zgU4L7j=i>tmu5chAM_p9`Bm1MI8!Xax~QjZqO`r+;Ct~+ zQ5ABryJzpql`J%}xPKmGk)QlfX!=&Wgm31b%V+rFnoo)PH*du+w zR}FLJ=JBZIsnlhsdZ&zYWNhd=I}dYh?);V4tXg%7#T=$lf^l8_ROXKL*Or}4zs72F zNYo3_beZj+Ynw1_YmHYyRcn5lzu3)#6V>_`Jhbq+bvfXvn}1{MQqv97B<7A6vpphn z*zo4lIQv-}U7w{1P2b2XxETB|R)#kX_jEB!Fh+U~Xf(8VbisZI3-S9B-%eOg@+P;kQBrmfzV zXyPK$6eA}4qU?;7nL&iSjq=uOH+|epzr|&zMHMbzk^X+o^BcC9dwN@I7YNUae00at znUfq_-+w$SO}oS|m(K9;XgDV8QKJ#-_dT=v#{HXKX}g574s*RiWP@E@w>j@fR+&F> zoi9~ksf^Rtl5D$jT+L{T6oX4js~d>OUyQK42=re5o8S6 zyMN|Otok@7-DOj%8Pv@S##Cu}d7iCuwNk=%>{4RhXuaCg^ueCcU0_DI>7-cj)uK!1 z<++v5W;JL;eK;g-Ut4+kVC>G4^m43}cg_Pd&0PzW;-;#vY)QV+Ak(e?(}OxVitEX!LWFc6)ow&0KvgDbX<&TMm03BSbEqn>Q_BQjO5t!++29 zwOSW!?td6IOI5gcJ@KT-TKV>uF`pGLT~SLS-?(^W?a^Ypm?s>gpgc1*w)CA-%exoJ z^&h&iS3~j5@mXIM#=M@4Roo>jS>h&wf3@e_d5xa>=%XAuF~2BUawqrMmD0~C(M!(G z%c)bEbH6L*z%eZ6MCe#CtFsb{Cx4tmMC$I&d^3i4qzUh_{96pQeCr-B1-X+lV_Ax_ z3AVTQ7l&@KyH1hcm-E0xvHHopl#uC3=d+`Py31#6)Ms+Y#LWW_RzH?l@R%`Hf~P>SR^M#A z`JO$OV{E2r8NAy4o>i;yrIhE}E7)<{N}?Za$>>|xhY)hveHv+|=t1M3@a%v|cU=f>EbXC_qCOm?y(R_^FOuNu&|F(q9KyTHgdV)K@w z`O;Y{b+*R@c8IO|Du3>y5V6F@CFzo6_T;s;Ps);Jq#l`2bI<$7u*N=p0CcBhQ&poE)Q==lb;J`KUv_*IEdYhvc zakqOBTpOn>rX2JSB0=a(dT*KGhp zw)HD&_96saxlQli`7mzRGdWqB++oaQSzCPkJTpJ%Y>}hSC9HH8TD@*OnI=Bf-euo9 zrC#&#M>Llh*p*fK6V5iaym~RKslG+ndRg34V_{tdNjWxadEb){Np-u4@@jFf`g#k) zT+_}kJN_-M>wnuoUf7j+D4&e;aBKwN>h*|2!o)(e&}x&$qaXUHdy_yI6A$ zetO_=ROgYu_qxlL4tkW^I$!eIrwW^^PY`zd`tY>Hg&s#2X3#*m_haXyD{Rdv>d*EG zQ4R-S#(#BG$zSo;-Rux&Si({Xnl{^Xz;OSPf^B#rx2Yv;e|RNh?~w(mS$&-^wA-^> z=QQ;cM#X52d8lysvGP&(gB_wmSlW{@g$5SFua*|>WSu;}EdRE{=WXvR4bO)x^;>&& z;ZnuPjE=V(I@>4hpeE#v&wn&aR)3a--$=L~ zuRQrvVs3wze{4}u#=VM5%%En`O&^*ckRrPJs%0zAv`^gYBN}j#^f@(nWwU%l!lBH( zl6?OojPLV4dKRy;THV;tzOd_uOKj1qb+zoN3K>(Y=MI*c@1=^?C312~V-Dk=%*K(A zG|VHVkL6}d>Q`@&G@oQyTRclTZkc(s=9rV&S}%Xrdmp;qlBi+n)c95RP~Pf2-}m!Q zm0x{@8Sjf58+0&%7Feq}!E()$kV)Yd=N8c9+#0@xek=&_%6a53Hhyrt0!{ev-9{tX zhpP2<&&)kfp0Qs_sXhMs5b?9yob}d|oW=*P3vcEa(PMR1=h3ZC&+N{6ufgb|S1Zha zut0y(yv`DTF{boc-q+>U8||IM<#*Xyebe)acOQHD@{!YP(bMn5*bL!&u*?q;7`p{KKBOkAzF z<*oWD)d$P8mK*yAsJZRXIkMQMUq+9z!PbB6c2@kO*k>986UIr0&KAqa*UKOL9%Egb zle9eU9?@aFN)uVV?sm|CNT6;gK4H%){hIV-v3t5D6TXcP+h$Km$k%w|Io)s`S!(L| znw77W8~1lUkerj7z-(|?EOLRlwJ6Z-%4DsM7P0d(wMV{o`%J6u_QttxDw_E4q?dnk zgbrRi@S>P=d#^R`g?hz{uh*{Dfv}Y^RFSdqGCMm zxW3p_@xW50K-VYifin)bjT(3Bn8vK#ujGTg)hgz#pV0Q*xw`7{u@&ByuL6S#**5xmEnKN8+U{ye>~(LpVS?n@(Ghl)p>0*%Y?R)c3p1V z)DE2FHt}ui_H{d8D4RVCb?QG(DpuGkBGVjOWTwAi;v~;^x}=w5r*0{|Kd5EDVxZp1 z!MgJ7XM;IqVibjiMqTDisj*}3*{hRwAoD%EhotF#N%Xs}hXy3Fo z^WK^*ym8!g^qdV9Fy~CK;hPz zC>h-`e%elBnh3(4XB=&dDLE?XxlR-HxVv}kZ93s&t#Y*Ie3)|6y;FZi)%jLj{Y0r< ziZ&9Rk#u~{M*oYYQLWwdg5-=1#-ZE4+_qT1ynI5E(85X6HI1hZQjgetm!mj-%#4|g zU)yoZ`f{j{n)!5v;|h~wS+(ymosBORX=|L5tPRP(oWJhjmyGIet&}@g*3Iu*btFLC z_+dasz~cDSk8j^iJLP|(e(`kSRq@u##o3#K#(X*LsorIj6cF_~Wfq}e%(D|M$1Ytp zeWWaXh;sW)WZxmbm5KrN%B!EmpCMk|dE-Q@!g<@C>gJ=J^XMnm-aRsVNrwC8Dy6%D zN^Ug?u?zO@3f1jCZ6r7Q%ls8fr5q>L*9XC7$mY zKS)o}ob`ju6uxlg*`2w|bxr#R%S5;ebMx;wq(o8hVd}LD#6>CDP7`z7gq?ImDq}+K z7|1bfQ$TOldL{8TTFfC^t*q#%XP$L_=k+Ox{@(T~qSKWn*7o^6Buvp@L=N_Zna?yN zulrH_5HBLl>uG-_I$vP=v>r5VxbW?}1kUW`B;vMxQ&i+ud>%A+-8x%pb%o*JhX+|P?S@UEEDc50)$`rPRYE_TkSPVW%Hc~6^n{KW_2&zJizZ~mR@w`%iGXZoge z{(WWsPkMjxyX*C6@G+?1k2{qLe*_YOD(DQkRH%cYW~Uo{IDs*8-68wPJi$X5gUaBS?2rd#L(kOVLka)m zh^asCW=P!g9R=5rh-FaWv4=4-1h8i_sQjKyEn|N$_@>>F4S^@d&^&4`I%l-9V*DZ1 zjP#AcPXbH}>L-JWQWkYsZBj>=5+ItIOC1#{D&G(TmTGR zzJ-4=6!%SSbXAB9{rKp-^eR6cy*%#kMm>T4>$d>F%?4Q&v4LQziT zS0D0u>!`i)mqUFX;Vzn-Q6(U77j?A@M4Nv~MNc`5Tw`VnhSa>QX{aE;9CK1a~Y$}j!=J8 zR001m(2T-~&|P2zG6}#8BR4l!P;(3vX$;f|JeBaR2+WO!0Dsc(tK6X^=JG)pH=&{ZpnaJ!#HRot0!~Ibi8!)?hr`d$aTyvNgrxX2M;(>`d@`g`|D**9 z>X*UC%_u4q*kSOyuD}PuCnQP0k6eC-rt&F_ie_?XEZVY0pbNR&hp6`VPHr1cLAn5&a#?2$iv=#%gyXVY=&@rjx2h`sUK>*AS_COBD}%G`W|< z^-shEzdUY=*}hAgzHR>W#wULYeV4}B9{hCq&6&gM`><1<%<>biSsv|nNb=C+8txPt zBPMf2tCZWV{WDyS?l9JHY@vN}3fV4obIt0Q^@jqR+euDd*H5q8K5%o&{n^h{r_TN4 z_hiR?C-&Y7xyRx652xGLSsrMMb?;4e(<@j z*oKneZeOimA*rCKw`Ianw*Dg@j zro%=VAF;*$mBjW~E~(h#)t#Wdl2_(>L`O~0JR))+_g;SfwDEu9TrIsDdqf{*ORFEK z5EWUWrR?T(JbA;wAyv8Z`1_?oyL1C5mb*oLwX1eM<8HQO$FZi0io>yfH(s>P>sEM} z@E)6aJKJC5#J-2`Z=GIwwYp)ltmfF;X9_9aS2LyK*Q}{F)qef;p8Uf5QEfWnYL7cV z?{Kc%kTB`!18;w??#&*77AnFCdp|c7Z|A%SQd%6~XKOd6!#}+G%ByAgFXPlE=&u3s zmt%^xCaq%D-&}9Z-gfo2{nzWpcklNFt=1mUQ!Nr-yJqq;p&1dyLDGwx#>pNKo~`S( zHguAW+Y3iUdt;W5qcn(HoRgt#n6aIF z?^Ct2_$yN3T$yQl8>*gH^u|%mt%8``fEBEZJcJ67I^H~@w2K=!i8!VV9Rc~Y&QP1SN^?F zuKPeq`vq&EZLJ+EU&a^qUNV<7epV)QQ|q$lx`u!8q$5Yyhab2umDqYaY4Q7qNfUbG{?V9OUz)l<`IKCD?B{dDVn6V-I`9mVVBPV>G+4(zu4 zCWWEDRNF_`St;*jJA-srRKnkL^IFxW+||a9wtt_fHD0%c;v9VAcI&x|3X-l9nd1Yi zh_Qc^qU9Env=>{SPS`g72fKGN+eB!4Ny)bRk&#b15mf1|j*BAvCatO&>{ideX7;x6 z)Pu1NXYRhKpH&x#buA_NUN$gUq+Gt#IQrz~lmee@ncauFg4Lc+ zYilii7t?tt?$AQHbIG;y>4(L7U(BR=_Y}=FnqcwVIo3JryGDo^VQ<#`EiqkBRwRG5 z`z6+pT2GC&IQzB7MXBsKZttth&mBL`_VkE2onW_5X7$7$Zs&Ei^2{z$=GPhJ8P}uGy*EI0$((Aoc3aQe4@vrGqEnW$G)p%Gu)oyYvJ(2%TVe0gN{q4kz(efobl7NtyIH(&Ky)4f+pE7c?t^2e5Co0ia5q=#L4 zbXl@Al`Y59yBbq71s_*^^-P^i;bXUHS>sL}G>d+m#a3AaY8j_e%r-dB4$iI+sc5M>SHYEh{Wwl6R3T)u z&Y0slfp1=X%d(bz=o!;^HUEDI`Q9w0o#HZXIt~4@+_i5k+t#k!J4W&Gllc>x=awth z?ve`Zd~|ok;C7Drmy@sb#iaJ8RL>7DU}YpIX)<)!n%XmZx28Dux~gudHnO@NH$hz2 zV&lTxx(ed=Pd6@!bQ3--Qwhjj>{d)zv~g1-dAUvcldt(Ww;QIrCB1(h6B3RpgloF3 zC)^6XsHVaC;-9RGM=o^5KiUI2<|iinYiUDfdf@TI|DEvsd$bAM8`*21yZ^_YN$^A0 zpY$61zvu;jrq9uQA=jU2PGr!(BPQXz|JQ|wAeR5BF8c4J22cD~r;0ygZPbDdF(~wFJlfv&RUq|a{s#5;_?46 z>Hl2iBd$LU!5{Ib$Nw@m@bmwu!~BLAM=grS|A(3%G5Pn0c;J7}gO~q15&Vn!zipjA z5Dq#2FA(^vy?-(I|CdDwXz@Vd0KbvCfM5l11-GzJ;gP`u>8Hr7s4P4lNirT*G9CmA zkB6zkL!Z$CgAE_)e$Z*~2Dk~97L*@w9%;6C^fZFUqtycB2?!v>2_BE=F+3pjp@IV5 z@|!z;i2Cu6*}#8jq|RWI^gu&|EP_XY2iX9R#0Fo62yPEN5)*y^z#lLUPCy`uM>V;J zpjw~p!9%V98-`*50uQ`LfC<0Ceg*p%Xgzp5+QU$K!^2L89s%$m#_%8oY=HdYq2G9r zY{a@B13hEmflz^k2y_7&AUu*#Jd8OaMUYbmj2W^I*!X{;{J}#2@KFUZf=42QAXor4 zAZPHfe30COHYqr(;PIgIu3sCLI z+LKhk!}`F(>c)dof#imWF7OdW3?4!WID<~`fRup`uUU4pHR||j!((z=9vSs}loRGl zJ$yW^DpG%2MBnS_i~gX8`6U~KK6-x9IdE^P(g_KJHS2XJ8yuf(@}rArx&ybWw!SCI z{7_9+KzaOi>7oB^RBOHI(cGk0EgH?Knkg13&OFwqO)d{JG zYQL){Ej%&%&PE5r(2tMEg%`9on-83fnQ7kZOuD=CU|Qf2mF+VZm1bPU3K_{OKligd zu#DB?uw&YjCzYO>S*`ng->03|$Ps&=MS8u=?P(`qgKUtTN6IlR+J>nNSyOL;XqU8V$8KU_F#$sk zsx`TL}&8!gusg!Wut9i%GzYOMd0C6+nX=QU*`eGjzF7`xu?w2ayH9Y-v0 z%1=ypZ<7ALw&if>!O{zGmZ?RI|S2OU(059hkl| z+e=(7XHxNxIm;J5P0xvSxFD^v=9;7R4yy{+$pPXLio&(?Z?={OTbah`i|kP6>wjG+^yr0gsZ`Ph zrJ3d$c0!8`-C6tbTI30jcV2&*Ei`c2<HW5~{AL5&y6vLI!;-dItREwO{*6dZKugWVr2$Wi2#H22H<~r&{SuJ4!`;c#O|%F7eFva5d#5W9jsHU*5iqoB^pURJ#4I!OM@YL35H)*d%~lZ2S=?*6^SmowfRpgque`&`yc;zYy>@7B=jGh>d)jaz$W z#>S}ytEY4@l8%UBHZOmFq-pCEm~6F?9;mJR?)BI~c^A8nd*>7v^Q4`c(lQre^z=-Y z`R`fl=pX#HX~U`M*QVV~mYx~m>&N>3{;hK7cfYvXFO4Y|I7j+1j3x30Z^L(}9hlp; z?nmHf*WKK$?`AE!@_PQm!)-H778@E{Bu&b}`)ueq)qkwIsg{3~-FH`YAUs~|{fj>B zyvkkeQ6j6eH%0n(pOAC^v2khU9`AjNLnWxYdX`Qaoa!3%&DHC_6wATsO)a|3}tQRe0&Rc&d!+M$NU`E*B1*5~1Vtp;`=BmXcO9pIopG1`=tf^J&sroS37`EwJJn4VxMEPBMn)7WIWYvny$}L|u zBPPp9qs@6|Q&QwN4)q7sW%`~=rzgLEylHic+uAO#nft9>ZZ>rXx;}Ef6%_TM(N1>B zxVbM}?*uqVopU_wXk$>%ttGCAGm?PH? zNcXC`T+(c~)_F?nqfY%s;Txo_JI4!!XM1lS=q z$)Sd;-e*)QV(UxZzqS|tuX)k7Hi; z7jl1;%jR_r2)S^J6r?*dF4YAEsf$r|RyT>C)I3(uZJ6SCds&p#730zbqv_jcMX%;G zh`-`a(%s+R&g~qVv!tOv#A#pA;Yu%NQnb;OgZFOvA3P&-Y}>)?osQd{cUY&!+S5Hd zPtW**c}I$|?-tE}@Mgi6adUd^yjSwyUU`30%PB-eW##991|x&*YEw$*^@l%bK6yI) zSbg4}<<9Tko|;B%6uF}I+%EZe-q}}9t9I;9o>e+wX?}Ei(ilm6Y?^qRoSdHM=k&2( zVkNKA$839Hpt(CTtFJIj=FD34>FGc03pj^YJ{YT-T^+DAzG}cv_4tGXkG=KwNlkyo z&UrFfGs9Et@SfWquD!uW)mN@dG$@RwT;IFwxTxpB?jT*UX$K`lI~RMtb{Y(D>2sZu zJZ0tmL5UNC_$?aK%j5>z!{$1SUBs#y-@d&6`G+kP0~JAdVW|#X?rZkCtvB+WE{cSm zb`~$XTw8kbhmGpMN{hh`MqHik{+E9b_Elbqy`mSX88tSMH(Np4mU^dox#(+aVO=}J zn+9U1x7bhEnYw3F`Iry6Jp&6pE3-~X_PEU}dny#6x#-#*e^JbU(cKF!Il6Cb+JDIJ zxu2tTsQ0aEj-#`PSlsBYpLULz3}67H*SN{Df<`cB?jO zdlY;-o9F8etC+cPx_6!ZvsDtPqSIJXm0z!tv;!A?is_&Qrylb)fq9|LprM* z2QOI!-7x4+R~6|j?TYugrR@c1__@#u1I;y%BpZg!eSb6|o|6SLMssXN%tB7OufBxus$?~h}EuzS3T~f!cE><<2 zsTCgh=z({uffeDHx>vz}$-k|{NT^G(@liS~I{A008Wk~30r z$}oPJ*&TJ^Xh8V>XG@Y?CYE#47xYI4+2`C8KG~+;5_r&c;@f`{nX}^S%&_G*R#~Q;%d|XkUO`M3?;(@|Rn^%}|8JYgv%oUlsT>nfne>pDKjBDoa&&d3V z{>&KunOyk3URZyQo5=vT_5O01Wnn z3OPBm6>?nvdQWbqod1eU;F|vmF1Wy&{{Q!juHOrcdVekh1OUV|)8EXW!2oHf&-5?m za(zHz{JHlEYi7o+m-7Kn z(5HGXT%8MW0seVrB7pJtH(SB=fdK*V05hOBa^M0pbTfZ1ziRx!=77<97)UdQuv|S@ zqaNG=T&uyeC;Yo53m6OSkHht0>P^{gQ@WWl?~_C{{_`4R}XUp zHe6qC25f^sNM?dCOJ>S}mFp$B3^^Eje}94g|G$6mXtKh`%paBz08?iDUp<=qf8hWE zi^~VrEEiS}KoIvY=ta5A|EYw_9NmN=(;>tFa%PqZXqe&e|KHGyU>UBz8KeFpR~J^6 zrp83Lt`%GgFgD-#0(P0StczHpZ9c35M*@6dzA<d80&?Z2*ASCPZ~(S{wGCAq{6!@J`i{@B@ODIPcnRjC)G*S>@7?wq zl4f3BY%ZI`B(Zr)1PY8Rc%ab=I0O=jQb1rsMr9M26ayxX4b>lo$tLg&NE94~Mk4V@ zOg5Q9V&c%2!eTR-P!5uSeJBSBOo)FW431zxCXj*HU{NTr%VJ109FUkK5`o7h!Gi&t zLg9fGfh7{b0H_fj4snq}V$s-a3KOP`L}E7Zj_{Z~Hkr)?;-1b11DKow0)|cEFez-{ zAA!vz&?qbdn~h@N*aF?XHO@A z^?4);X^_Sg#D>D5qfL!%PsXvKY~eATnZaxViHidSizC7IiDBc|gh3M66$N6EO=B_} z2pBqzha<5`EF}(wNx&&#JXsj90thmNtwdpQC~O>ogQ39F2%t13jz%D`4QMcMUcrLh!I?rX;7cerj}F4mW8+wC9FBm&fay540f7k; z0VD;FOQxA%3fN2&u!429D`q3}O>F@Yig z8D}y%CIk!#?4u-F*zh{N#cWKe%D6niB)fxUzRFwQ%{W{NQxbR3TDOd$+XNEkLo0cU_{cnX1l z15svC8pxU?CP*l=fWmDc+0zCgcv(EKVIGaZf4nP^e4{lg(!G2sj+Yo=34~vz3@EmH~ys zqM)#3@;GD?3B;Wl%%qUS038fc8Yw&$6N6f3v)T6cYzhQE1>}iAEQwp|3;^BC(BAM(00tEwJ@JJj8S0G@(1SZ>nNwcT0Ni-6nfdU*h zz_IN~WDK1R%3h2OM>G_WN`O}Y+#C|fBSui$@Weo2+jE&rCZHx3orVD=$-?kNf;XL1cuB6p<&t+g6$zQf=t=-2!O9>pbh~& zfuR7@I2MUTXK^4>cx+MwnM5YRGR4twWD1YZS5RD24A?XR@QqEP&_MKHaoV#7d4nui z5nx#*7&Cv+Ht8ThBqoImN|kVPa6g7YXP59O>;e{#i4-n|&cty*T$};ecnwS@n?}I^ zl2oGLR0$wh6c9RC13V^s2_SJ`Ude#XX4%uh2PF)f$741OIs^J*(wr%{{R9qg5E{jm z0Q9f{&S~TVP~ij$$pFW~KR(Ey#2CgLJ0P^*03Z2a35IF2X94Kcd$nqeG4V166JwXZJ4)QJ`zs zgVKM1+{?2EAc74E;tdoOjlw3Fa7@^21g3)&GQv>+BuY?UfsRBb&~N}%;43Oal~+JkWdnEg3ZJL=SVo7 z5?}%vjznhBK$+1vJSDIt(1=+K9F2`3K}dfvnWq3~IjW$T0Apc>Bmsp-FrZ@)G}{;8 zfU5vle8tKl@dyAt7+8!17V8uVrv!-@q8Ts)mkr1nR2l^b0%%VmaR4hZ*&G7_Xa(&M z$Ap;$J%$HR1DadK3BdIBU;t>Y0I{CTL0F9jO0ulBqj%t3W+U-VWNdcc4h+L0g|L}NQ^^ffPl(M>;a-dOhCz!fR->&0ucug zAPkau7z_coAE_8R6y5{^xqt#I7c_r#pn#O`v!;{u*lGGPIgq{PGUoCjGjj3g7l9f*HIqo@K1 zQ7D)bJXJ2+9$<>iQsn^A51XC=8&HEPn`}T%W)1@2!Ojtk0pgB>!ErcjAo7^7>7mL1 zpa$Zd1^NsNBn1>N5Ka`3TnYu0DlQnuq9f`EY~P*@x(sNeI2?z-;&FJOZb<;~4P>Bv zNSp>72C5^_0)cE}VF1BVSfGDr01mYxx*2kj|5(BVK+0;NX*t)IYv zVhG@rK!edHLFhOK17*krLeKzsNFWoGcw`KL0!bI@PZ9}70>(jgkimFTFb1}|;noGO zf&w0(WJ)lM5y%n3NU%R3{HbKX1Q@i(ad@5>0@HvE41v6n08*HsB2<4#fVhCn0p&>m z?S#+({*!11payw#$Vi}VktqfCBv{*k8Gu3n@`+wS-VcQ`odmispfLbqC7_o2KsYc!ZNf&~0N|V44f2V!7B+{>VJiXU1sD%#(*_>LU_HnR%RrUH zf@+Hm$czIZaS}sQf`IL=lpz@~>NA zR(g)Mv9U^O7HlBKmD*+xb5ehU5V}RWO1ecVT{}mGLoZXY`1CU`(FJLt8yjn9rK`~Y z(m?IhbhG&?_p@?R>A;cHEVER)O1f{lHa#mVJ&XQ_%3WLKKAnF~SNS{40LJH}&ezUK zRhh5iOIM+%tEApfr>Ccao$Jy$Iok78vgmYyq6DMLa#V7((^ZU9RkEyAj8#-r=qjmn zv(!JtAf0YUr-NP4hsywXs4~AYOC{AlJhfSrX~uk>jIUH!)Pf;nz~SN#@x#vr^XG*bP(Z2X)5=+-U28+Ft7Yju+29JAByTPRID4!~XE2^j3fTw~;pY7@al)deJ>&@xHvp zK|i&*5v?&tkcJO^OfUiNCX0F_Zz8JFRn&zimbrDNyp%RYL)^Z!rx8oEd_Re2xdXu ztZ6S!mcB!p75dm~SJuCzg`WiT&%Nw%(%lm{dm(?yoPL`R563(^s1mO3Do%A7UF132 zR%qXI`?T!Rv+Y$BozcDQGu(Uch;A@8C9TkYOF}{I=1YbiL%nS4-Z`RkZvOS2oTP!a z{v&U%KaUEMQL{Dui@iGDa5cyK?UFH4XTC^eaHqZx&YXJV;6y$VjJVe_qp1Izd1XN3gvHH?|*8bksVlz< zJf2fg@XO-sUgc^INZ)-m=h(kc0eJbOTKNUslZ zDpP6AeKvK$JfMH>GWYsi9^A+q!(AXz7CaW4?fM`%X5ghy(4u)& zk!q7~*iLFChI~9+j+yf)GsC>fzT(X!vaQw1`0cFlT+_b76JCi9Pxh^uOIR?Y?lt}G z$>Ya1R;4pZcT$y-&7E#+rhF2ANid1-U)3}>$6&I z)HzrAnnFb8l-6hD8KLVIPLf@8@7$BBR;#(b`UlhsYP>vU`>1thAq#!Aw@r_#37x!m z$p?cb1v~1%@vCQ--Cg6q-$#E$#bY~bpXr8~>^<2=<$5dPl)t~SEfm_YnmudcE$*a; ztr-W`pR}B)GwU42igwYiyi>^jibUGAiezST)Pl1VU8+n~H@lhVdrDhEP#jz1TnXm@wN zL(Z!v`UkVbNtS=eCe@E;5r+42oOJbgP8+qr4N?jMe8{1edJ1+Oiz|5@M zQ5#Nfyh_^VxRaZ1+Q$sHbLGx5N_cv9Ue4FGm%c9YK6QUtNgLa5quVfjVZ)r~@_XKX z`0@ zF@iA2qk*ozRR>vqQ&c9s}yPm?NU3Gv*5h&;#bpe_TyLYPyN~>i52v_>lG;gkU+TG^{r1A$Xn%tK zhduVPic}@BkJ;{)-viSxT-@5#DtGDix+x3GUg>|%%H*6B4$K+v;32&4p#J6J6RRz6 zHrj0$+W6?yosVb7_s(5womi0Qv-NV6*rsgn@N3Qg4|`S~U&HzJLo{lup|<9#ePS8W z4B^HywOmw6rIu6{v|_!MUy6%VXb3^+?T5gcoLWt7*ED?>O zwIzSN=R7la3%b4U`^WqFm`~h0%QNRW&pF@oJ>TcbRz6zs`H4%Por#O9cJ#)I5-tp9F2J7B_jG*3vUxRUXTiS)+&GnT2Fto zK?0~iSQ?5x8@yQ{>S>?0;_IqR9R{-D~^AmMTTctUc{CN`i%t*&I*Wd=r2~5XV5Q}W`Me- zT(HAQJj4|c!qPM+KwzNzf(6G@&jcdip<1x;a{*nv2+f+N4J6>B~d~=7g%%Xe8?54Pd*T!VKA^F%do_8JE{@E7Furs zAW4A;c;03a%}{|1Y)oKzv>7I$3mK^8K){C}vLKW#yu}J`F$|=E0ZtGQih>Qsz;M=L z;q**UAPY%mMO(gJ6dAZJun+^@pcla?`IQMKv59)X63t6ynkW5B0(5_{w1EG|LL?hR z0&I)CMPei-oTm$U4!0ZlYY)R1>{SQ05qYSl0cj#bD&Xu+{-2m%#N%25cmF`zi0l6Z^?I2;T$V1w|1#5L#xAqEVu*VDKaK(_#z zV4EPqswxifnB^ci_#=OqS=4kJtLk7P3;=?VMu?!mS$UKaqMpIE=z|#GHd?|Y)A@l2 zg%PADLR0|y__G(#2s~7^E9n<(0cdZ5Ww3_|JlGK@LFt1tL{j5c4FxbwYi$$inaR=f zK+8e_Ed&~XRjt4%B4dCJAjFat3{b7wXfX)YkUj?u@RCS&F${mg4rDc5z;jH#!0CAd zgI$OR63PK5Ibh1WoS8RpJn2GaU>eB^C8YHjXpanIrD-$r56@c>58yH2gJW1M3mUvZ zQnNxO0!{L~Dy>+k8Nh%U@Pc?IO&Hoek{Bos0Z_xk29{_jSc-`9mPLS+(85?Q5SAnb zVKK4>Gfsy&yqEthh$xSwUiiK%RwkfgeF;feYu=4h)l67?x#_xgohw z!U7hBUJv_nUV;!{_<2Y7f$A=LeY`$iAFq$s$Lr(u@&A9}P-SXsuMSEMUZPB0(G5@eEQzUwKRcxug* z$mymjNmHxfgTo)H15`yk6wWAR;h~Bb$jy?j+Y^33Rex*ip=Fw-Q6Zq};k(I=GlNtG z=$PdFz%74%J@KUH0Gx@{n?rD)dBu(?2t4Y4V-(FO&)b&Qj5|jog5t1=fuk3w_{^7&@N)T#N=YP1a2zGQ#Gr0J>x>0JbZWC1vRoe($n$A zgkB2K&qZqsa=iNJ%Pl@jBz?dme*EphjbxO3(KFv5yM)?9@>*A18a?_<(ifGktCC(I zHb8&4=geU<5AGd)>rmM6RLA5)*AH}a#^$rRb1kxqxSTk|CpoXs2k*X}cYprg8$V{B zaT_}s!{6n}{V`vtZy-n-9oU4?ZMHZtz$Y>eW?eIOv4n$jF-=u7g@LMiR@1SS*g4O9 z`k1zc&t1~|QQJ>T?bdIE5|NC1lKJ<{pTmD0e@u1qMmXDF!8hM%een6g8x7#94i1mK zI2ON31~=IjTca~*>38t*t?`e~thxbO`u_d*Hzhww=!6jYr5kJ((vNIu^Q5I#5ORs$tx~&ZU1R z$T*Uclu&Ot%^A3L$C4`T)`9<|L}m|q^Q<$BZ)R7T@`GDo5l;O|4)|=lm)l;N=k`eN zdVwh4dq=xq$mgh9&DLz{lX%vf1dEi0cYZ7QoRgl~;8w>TZM2&jALLIODLL(>*iXE# zk*|byi>htIw2g};euucZQV*cgwmN?%9`R9mh~F7rm1#2d)S49)Q0_ch17O#b?nwn8(hH|-Dc**>;{#N#X8qMf5*}z(h8?L zPkRY53#Nt98WWt4Zy2>;43|_@eVSOIQ$3IC53k*B(zDijlX~xBZ0fhOUaWt&rT$t? zUF3{4X1_D7)|X%Ot#JTf*f6*1Da*22oqI%0a$KP3+jft5~$%f;b3FHZ#IuWY)+1wvm&y{j2&J_C(DJYQIN& zw0Yj-rr#&~j_JpiM1p zEeqV(0gpiG)bkM?49iwE-`TV#vIE}SmR|qH#`}gU;|X7nYjs=)=&b) zJ;O_`u_Jx^H&2&|8?+4QpUi7|U{m=Obb!x_3Jz|^BNsouA;XRa;OB}eJJcr~`+Bm& z4%U#TJ6-xHK4o)8NYm|k5!{)#*14CN_h*%l&7lC|3x6fZ5!=&J!v1()f{oqhGCph6 z<7woaZ!^<9xVpt%)zW{(IQn1^e)+j|MKxn7_x*d${%TH2MYJ)dGk%ty2p^d|G9$0L za&1H=b~`dJ@>H2-mQ-FD_i(HH66?)Iu zF~Hn7VA14jIz3@9Q6~Oxl)LJ>&QbHy;vB_B9Ul%Bxg=KJ+>1dR@H4^?}|! zu$eaOwCno9bD2O0H+#=R>>u9$>w#47FUREMV&*VSftZbI7CAG4tm=8w;gJKN$g~ zr?TpH@1mC+>XZ?_KU?N@XdY-MoPW{&hE*TL@BWUAsaYI+qP$1=$1&R;4qo0pF2z6N zHuCF&I~5DwnSdRC13=lX{QdJgTY!pryWfQEmEhi;-KVA&k(v9uP1q}ae*F)yQ^cVsxZM-YrEVz;x=Cydp zsVxgGUKvqk9%5%-6zT5O?pLbZ!Z6C>nV1$1#JNY`IY4}Wdi&Y@Rzzf`Jw}U@rur2e zyrn83XzD@b{IJfhdt|QjIHE76pHG20vsQ$v@%#Alm#Pzc??gwVo;f#djD7mYO}h;u z&rpQM&0X2TWP3{*;@jIn84i8%d(YZ6pALdDAG0t&D7NV6<`>mjvCo~zX32h@vlD&8 z`c2B{vZY&pS}Whw7X4bMB-xH%u6ZxCYuxJ0)b^E1*DpN1qbRTewfww={gO1FqG7`) z6RMk7tyV-5T<07mNxhAqORlO8a?XY~sLp z*oPS5fy+~)Lq)j~j4_LX3DF0|C>wP~$_T#Vr7UNm1NamS$vO&LGb)r(k#oyxC!^8F zjYi;qbuei{zhQcXvOk3joeZ-WVE~nj26dE=Oy-t#MjQ?wY9FCw5(qg<2X-ikg6g39 z)R7IDDx>I)Iz<7q6!quA6>q~F3R=d*!0KcZS-$e~A1qvJcJRa~yJ{$fa%sGZ2EVcZ zLTMu_K4luPLRurP*eI*7yr}GriphjI@yW`6Dx>U4+9Aj$2&{~2mtV-J4iW_J%enH4 zu#++7@P`lO|na6p;MRvog5>}&J0k0F4IFfDcI5g=W>?O5g@E^c)nQGw747@TMtE& zp`*%ZX$lHJhbhGc=oFJe&`1CYEP-hzB@0VaNF^W#IgOhrN}bRx&9ZD?pU$aZF+_?H z{B>!IGEuNHQLrXON0sfR5Dj1+@Pf*MB?GOQ2sE-3WKk1L`#46?NVNGc;18pT0u2OC*&!k8av^RG0qT}o&Uui@ zv~zg7=ml}dVSzYDzj`C%v}u=r8g8@vA1Ff2xU(OCIxa)UWLc0b4w1vXS_`0yTCc}f z+F>@aiO@Gk->NBnSvdN38#zg3$7#3LFO%T;OWY;vYcAe*Asb z$G5aa{~>hX)J0u!Wp^CTx`a1a7mvy`5U%X;KkMUau>MFNsU!YC+HjD64H7zc1*gRm zQtlpL2LR#XCB%;a#E81?O57K0SMzF`9R$wVcO_&!f5ABS&x~_!bYZ6jrv-u-jw{n2 z@X5XM8l zrF$H8vR8tVQ`_PHPTSOfmQ}WGa)0gkdz!Rf|9(-U&mCI(M*LJZfjiy#!{v!hCPden zwkU4$H}CA(>Um<_zPwG>s;%@298SebLl6UVOy?*Zl9_ z-IV9emYa2cNSg{1d-(5vx3u!Oo5O!=)xPrBx}F{#x3>vjnZJ{*^w!Nmk;(qQyl?(x z&d^f>>rI+xY8%O(X;tL$btDyafxEHg^!l#?!Zrp*>}@f#ex0u$Ec^CA#<^!L?zHNe zaHVlXx#Z#r3yQ;kM@8Sb-0pn5NA9De4Xd|1R)4Ga#g61S?3v6v(hXoLq_%TObAI#d%Cmhta9DvTrpn$smaRig?(>Y1_f2+OLrge z^s6gG&ph|*wXC<3!;{i7=J?#uo$b$7|KQf)_SW3@KDiLsgy~!LlqLDc<7X%9dkq-C zbhoidrFmZ~yCeR!N*X$$`)VZgw`;@`&qb>*wp>&F zzuC3YaH_hn9f?TnP=<{RIie(zD3Kwe6y*(3A*x-H>S#d5w}c{@XCX?+5GSE>N`o8{ zp-_^4iWHJ5Qr{Ym-tXu4@B3q27iXV+)>``+?)zD5J0_blHYpdmP9Mv6^iH|Gk)^-c!D7>kWyg*$ z*V^K=!^*L%y#8&X;vl*DyjW1+u z`?F|%?~=^Za?Rx|@lLbmqNcnyXL-Z#5kaPl8=Ng0IPJy+lJ`su@LlQIcf01BR7lHd z4bfL+>N3M^uUDLPm37-G^2zr_OhN!x(BjfOmGKs*1-r`nQzEvj%O-PPXL#%G-Wr&c zyjG_!(LuUSOXj%No~#}ND#X&2_AvXyU-xGkRS&C|Xy5GB z8IOL&E4g;4-}s%CQ^|(`MR#|E2k& zVhXrmP++e{P~fBi4u)Vi47S_gE=3Xq3AUMk`%}!nmvnGy1sg^<7W@%7tqP8eB#nIw zG#0!h(V?8AC=yNp@1Q4wO|2RMFDP&dA?S3H00#>O0~{=v?0h!Nqccdb2?QqzG>$+6 zDJB!h3I&0A7w`ckB`C1@&cxY&Td*Z$pmY-nPP+ut7e5cCt$>g~X98u^V5%3*3VuL8 zPyd4%a|V=^V8I$1I5MMNV6G69*@m*Cq!bzsw8mplCoDO<1jE2i7^ni(r@-^T34lVq zh!3y>Nik7BYJxo9XOM5m+5)qg&k76N?&djT;~bpb*FW+(6zBrF;+2|5E;S||c{hl*mSBI%G( z@sB`paCkwYQ83v6FPPYR?2I157X!`;i&9KFN}4lBCKH?h*aA@$4Z;fLADGaiOl&Vy ztt1#a1i`}Jp`Y;F$VkwCTnq?G0{D@{3P88vVhBnp!npv5V6ln~46*|t2Z~OzfWi8i za7GrkF)Wx-6gd2l$a2#n{AWb~6li1uCyIz^zhQAtEQ-xq0;-11Mxl%x z6Tt~8!6azV#UzVjB8*I97TE&I=nIXA)PZR-BqbeFjLw8500x^0bT-D2oetmBsW` z0|QAyjZOf*!EnbUv7G>$0ErL*S#$=dLLMhg!QRu zPE*rV1%EUEUi5MTfmuaovtybh@KFR-nSz3|*onUmwu>l#WHQW599?OMAZ#KNBwQV^ zKY)G&1Ct6_1rr?#2SE>@zr~3G77w;;2g9|Va(0|pboIl#2I z(%S+34YY#PAV@WmL@^e}KJ+dGCJ$W)JqE;0I#3P<5@-? zj9WATzJw)zTE-N#j*C_($+|il`-;#A6kgC7m~{l^GK>$1^oT$Z z&Hw=v3-)DEm=_cY7(lYu4OHkwW&!E}!3SR8We>hN!GQLn&`SqUR2*c$>cc>b0UP7U zXiT(=0FFimfM#V)2Nre^qu9$dTqfy%<1aLI2u3-q_@a>Gi`@uc+yMVc&|3}Am5y|W z>_i!V^8Yvn#13Ji$G{OJUTZ*@h2(|{2P9SpUf(FZ{=z*1o$&#>IuJ-khY>;tR{3$6>hPf#SB}p!0WA1t%xyDCUDv zG(=#!FMgo`$^l%@Vz75*7|s-aPiJ~rM*AS}F9e{NNyqHPvieeNhjg$5FtiwU8tess zS1z{qnRLh>=w_fG{DkFbible>8z4wvY=t0XOmB(lu!zKn%%k;HcAIqTp9s&@knDD` zHk=BYI-q>!+DX&Iu(cj$$?n$2J*zV`tvUqt^6LD8j1|k`*6i|aIAF>1<|bzlmt~6@ z$D7!imB%^~#ba~I4UWHiw7^vS&UspY_HdVOB!NtIuz4Upt;p1w9Xn;yrOe@NHzkVB6e);pAn12@icW zH4RN`-E|gA^V#mb`7$X|%JhMMr($+r#=rIHJvR+(PYWIQVKj%>%^UKl{UEd9;J&8O zkzupuW zh^%>>EFrgTQbkXBh5C=H9{HJnNry*QM7y3>$Zji)*Y@-e+?8Dv;jFa5Gj8X~O{E3H z3)|zhWV_0(O4`32`22Cl`q28;8%&;o(Vc7cKlQwSG}>+TW0|Y>u*78Veq9@u`dsVH z9P(@KOr?I%Y)ms%a&I3L{pU_^wKH+^S<=@+36}1Z3N4GfRaBtrso=eT2!~$(G|$a< zY$Ug@R5Ehi>F>#79N7PaNZGuL%b)q#?~gX^aMfqkS{mTpze ztN+?}qHiM8N|+KCm-DvcvKV|XS7e?iRh@84!nj9|r_Sy{yycdvr_DUytsTZP#Jf9+ ziyW@(i(714@CLq<%8?B z+@|h7Hp^~V=GHP{Il0+;kbh#){(JXVO3OU45)`*Q@Lr`et$3E(XZmu{<}#6WDFSD? zEY=Jy(OTzqIGV2drG81&D-O#;q5(gJb3AmM#xh@--rD4UE!26U@nt#RJxbd?V_IZKnLlz!Cs(VJ14kB%RGGgRuqlQUJ(sj#6f|GVw#5#!>;yjcl1 zxzwL_L_U@jlq8Sy%|5vJ_Vx!80~=flXRp03DS738{(-kk)<>+kCY_s;O4ScX8%Mrt zGt(uwtKQddT{%^`X!6VX%qop-u8GAGg-tyTy6)`?sd>iUy_FxNxv#IX|2Hq)UO+@h z+^1jlk-y-`+}kMwYg3hsYllv)`*`o(*?TWn<*$EvtoLJE!6}y9s>ZWNIAdD4eNV`TpAZut$Sas!@kFc>c^}<<~v=^2%H>$`(!gWVea~f2BjWezD-f$Ej3mdt;;xe zuM{He)P)X@N_xE8uKIpahdyTk=kD02BE4r!R`~IUtsuVM{aDdBxv<2xgVRD()knU$ zS^cSp#|CbH1}C9C|ojMFer*AJ?rT6 zlO{Uvj?Vjay;0k_&ZW+HL&Qkv$ZKzD&4Gz^St%em8t*?D*jlAue@?{Wr=6E>^T>C( zksfWwuzrrYvHbt2I7&{vIsY;mqa$m79bE%u z-Mz167mBKgS|;hw9pAY>ohCP#I_m6^EO(~dBbJ+YE5An1br%!Evr0p?m*m^L>~ihw zPn&>SQQ# zkXEtcw|v=L^75hOq*#lZKln%nKsk@ADTU9 z)LnaEa@y@=UBhg(rF>%- zxXou>HdAr`s$ATEpLgUe5!3N-zTiJpwMbmPQqtEaRjPZwoXXVKsbBE~$nU0Q<^6D2 zFr?(7N+~JCcPx7{beBa=)i1*cy&Ls1of^jb{@5MTZ+d*e*GB5gediLq?$JJfV=K?DDVrH;I> zmd;^aaws_=TCIQe_OfT^VtATr_xl-ot%~vPd(}+}A3S3`mapVKubITj6zuUaAH`u;{%(t)5RlizmU zR~Sl~uIb5tb9Plmi3VpYG?4kyVL4B>Ys)sow(y=;HLtxNmeQIPo;+@RLpf>8ZpZfh z2X&hsIX(%Lc=BZ68ds-vC3oB@?hX8Z65@_X=p2F$J}Y7r6CZ56%S7Gh7vu3W^COo7 z+N_xW!+Mnf#F+m7x$kDsROHB!%}u-Rbw5bXEkfgezOQT&+C?@MUG7{V9&#k1MU5-# z%zZ>*M|RLSUgRdnzU%)y&wI>FP1ybaqG@K{=Xt)@=ll77o*jO%cNe~&^kCfMILoVy z^B1$~jOmFMXPtR7JSO(u{^>bm`h2=Jp!^8YsR4bSkMne2^rqr>SuYx1kDL~Bf^wC7 zjGF0xvp`jA{PQgnckH>k3GMv-xp2C%-s*C#gL^;A*!%Xy+o5AWpD5_(+A-fPA}QKu z_vh1<-6p!-E7;ro-RDhNu}TpaHm>Q3KG)M;Mz?>qsPPe9we#NZ!k4`H@OHN` z=wh@YCiTGmX?r)H?!CBD!H@T7@x&WZ(@&0nIg~%;W%HlkOv|a*R@M9F)Z?c%ZB5uZ z=~2@Vy21nJgucH_o_s4Wy8FCx=DdXRUEDlcb)0@*TD_8`UZ-`Q?mjI>Rh|1|%&znw z!or_#TTT<(=AovC3i} z^@dg|w!yAQ6lyL!XhE8pMmP)50;%*&#qyQB|{&1;VS za?In0F}ue#xi*Bqu z+?beBcy{llbWxQp_jWnU#%ZEb@96uAAN1+FFKP752M?8f=Y04qyEHK6)vly}4U`Bv~+;|H3Sza*~Iz$-hd=lO(0l+QeHXQfYEcDY9f z?)dJ{Xm=`B=Pqd(HYsTIwB1SJ?qPQmLQmKA?fK04`w!_O^V;=uCaflr5^4R z5fC%+AJzO}x3-N8_dnRtebx(q*I~sj%@Ufqw#^Kh)UlpH+s`Rxs&fpt$^Pzav7y{z%}(2Sm`Kecny{MkvctT+@*Xy1S5 zsVaYND||9_gjcQGOC!$~`*wM;(-_+8dd$s-0e2#v~yK}G4XMD0Z{1JWF>&Mx>`X|hYJoW30bz>Ga9aObL#r==4J>_Lhu=fc41qoM>cg5yVZ z&S`)D-FEVbH-A(;p4KWowT&uwYDoQG8@qn@uE-@jyXrsFCa=_+lxrW&Jl!eyeMd$6 zp!1!Nw%ye2e7g8S*r@epL*H;imf!28)AKy^X}_VZ{aCXCG)#MYv;T-clGp6 z3y;4a=e#4@XWQZPesFQP`#bd#bzL&IR!59~crzev#Q4gy&UJfs)RK48r{?~9PxlU< z=Sg@~zM~`fP5)iy+CxX8{u$D!UeDz0X6xN=#vEV%`mxJ@O_!)jH>RrQb)LWSbWKB- zt835Q7#_9KeD>mKOUg;ftKy;0icTi?9{AJF(OxGib+5G~Bh5It!G`3Sn776B%lYpk zS8S{F_=@M7+k0xdw`;OU&~$%te8sl$M_yge|2}Zw)xw3HS{_`x+>~=c)1k$$>HVjl z``F>!?e!ynXVrP1e)X{P(A17u!v=RdJSAw}dUeT`KI>ivZf?L2kf80V;1FV8W5y-s)pYW5&ZBj!mTl(p>AR+U=* zV%7Bc;xjI5=GKhq{mwkS>TFfk?~nGKoZ_|YP=cceGZL=fPLMH*Q$vq4(bHPYO5pc~^Hu zf=j^%MZ$sokyoWlLE@6QnEC5tiqCjNycvCeAVuRetlP2Tn=ZBDy;|1mSlN8Nv0Klh zHP%$t2KM%Uvpw{1l(#EA?IMhLeQ)%&qGt^x74PZ$K77?7b6e~WE#(W{R&V#V@77CcqET+ z9X2;JX4AeMy+*Ig{+P6;e`^IyQXje#_xkF#U6WGoS4jkky(b_7-aIAozAip(bz;ZM z?c*d_1Aab-gk#eP`2yMg+clkuFowjcP**QqMobQX1Dt5 z+|NgspK846X{UZmH}+VUeQ?doN%@`DZ2efiw|vUP;t-cuOZ4kx$*tlFosSJWb)rV& zyA9fDqz}CkGhO@+430?Owk0F0VfcXAHMVw#O(ui|*Y0}wuXefLR~^#t{?^ccTkU6Q zSG6s-%iQ!qa!!7f_qd7m^UjUvR?9VKg6_hCE(2eU>O0EC^e}S4z0|!;t`&u-M|K@` zuw9mO|He0)yjNY%|d z1U(K_kQ#a`Hx7uK568WG*yGwo>GK=XwqA|q-^>$j$Ovrc z@AB+Jqi&O@M5QiTmpi;&l*A9zkhg5TYyAC6&Q(8DlQr2KsveDMyJ*Sk#f3w!B`g~L zzS`u1X`SJ^^L{A`9hd^QXcG`k+N{j)!f`RMKd-t`mnHZPKiHCZV1V@AK^ z4Suyy=u7-5$BbVsX3CO(Ls`xk`Rjxzfk;th$rfw6aS>p7INR= zt~*3yQCLjoql3QEdQ@4;%|vl*h^p==WD_H)QIQztrKl1@f+|C%mlhHZ?k~uD9{e4E?MTpKxmjb<*zktg!a!3bkikI7teU{eIV6jrSklckhIzygD^I5>wwN(GB% zigb-5^=p;1l!bUoaiu7sV$CE%vqMIkK2)iHd2go{%@igULh2MI7IcEG zqGp7q1slt!9M3)z#j-BqLF-_mSaD@>)Qp^Tc0`>P#B>Y`n;A@CG}~Z;LW(sXvnSWD z`8J(ay6BV{fdn;diB3ad--$4hb~jV@S5b-cXuaqMlPjNb)vXqirg!U;L9dDvo>t+jy zgrYEWn<-A`P|Qw?h1qE-F?lGy33%;pf5o9F96Jhswe zqpSnhT%s>=&X|;Odnp<~Yq&OI3BdpF%q?5B8;!OK9I(UdzsUDXG+hapjZK( zjn+9~g9e(d*0+ENKun1=uyV6Ky}&YQ5;~$6Xs>FGz+tf*005R8&k%sJYG3L=ThH;O z29U81U@6H6{u8%C0HKXAVRQ=MhYs9K`B`A652~3Ug_xUWCxD#gW%`P0aP80oVf)c~@#?VthdB%tjFy z>_dX3CmXaK(6B7VfVKhz0>}F7D-7sRBV%5riJ16T8X4O2F8tzQfc}z3MkxBX4Fha{ z!N9>0umT08`Cp%I%X|=iaqyH$k`6VBRX>zgs$WGRf_8~y0%#LEjUZSCCc%GDxR@+( z&p}{SG?3;x(e6HhKJ&D|&3T3=0rvGvcL7^p|D~k*AI#}8?hPem7vQctC3^&C{42#_ zdjz7IIzZ*u6uF(-Ocw(FrU0PBn*!i}85-5R!*R@?4K=N;9^|FAa<| zvB!N0A+r*&+nK-EI@Gh@z*xXKfj@BD4a5v>fS1r$2F@QnIgTvTW5bY%1-%#nx5hw1 zq!~XU7YlwslfN`Xdm%PNll?S*yM*{mazGncPoWW7V*|`%yxPb(OEcdyYYaG3kXwS^ z(M(vx8{#VDj^;PB2IxzW4WDt=!1x83RYuMbTNs=JYK>kjC_hAsSKM)nu7{Hj7Bx0nT$dkkkc%d0pQw-{Cr)+ zuvjy4$>9=1bLoOi0DyJQz#OeNvhhI7C~UI%a+38pWEyV{VUIN$Se)<`;IhCJnzPTh z4G%Y-0c^K1wjfW8kS{vZnl#(>Xln@ZpXmo^WRq-QmJ)ho-;#QN;f+R)omqe}4FcJ6 z8c0ZNu8?;e+G(Dm_{|LE0GfC}UYUUPq?Xn`K=sBor@8vE5f~whIwNE!0nbkcHbNuu zjMj5T(E@G>R|--kn(0;K10Bm`nFTr-LVVWwiZE``{BVM7-$Em8gB7xi#z|#Z>Jnx- z2pMCLahm4niXASWkmsZ^>~q1y_Z0+n2@70HDP9iaiCK;$Hli3LQrinJ5ma&iSaL@ks`lp3j2p|P2dB8f<*)+)3*iB3ZlszxQ2Duq}+GD=6H(g9nZ zQF6!vDwUI5pw=p#G-|a-rE!ugRVsy2C6vmQVx3qg6RK1qiA|Raa5dB#p-@HApi#+H z3Jrdsa6WBTL z5lY2!G0GzeOC!)|bpnYaZ*&@_H|?Z_Sk8y0PzePBskFpM6X_H(nN&q`A|x4?X=N&% zLZB3XVpS@YYK)Bfv~7De!lOnYaT19{60{j#Pn@J08+yi&l@MTBn^PlDScQl}#rD2d z=p+{lQ1S4|N~Kmo8bPfRNJ$5Xg<7prtwy#4GMPZBmLRtnQqXAyGPO=;ZF40}Dy$I8 zRR71$dw|E3zW>ALOg+<=L~k3tTfM9vL^tYx>Z^_@QKC#(!XQYr#jr|r39G~kLa?$k z3Bjsii89J&tx-qsS?_({&p9)bu;1_R|6bSozSnzQzacYc&UwmRKcCN&Eu2iP)ftRB zUZtmkR2uXOWe&v)IaY8c1*hQzsx(fc_Zh1($#v{(p;Z~-p5CM~m}DBcT&0rpP-9Ae zr8;LXs4>EVNr`}`HpzWTQl~WV2CdR$Wca0!4mBp99MsCxN=~LRY86a7@N%O{)Ko@Z zOUVlkR%rZ6*=V9x@}4S`9LC%v@Cu_*2U8Us)UBY``i;Urrkr%eLHFo#qB1--aatAG z20=G+3SMrK2}&iWGAU&|ZxE3Syn=;)eCmwIm3!jI$-QMeHc8Tw9y6qOfupgy=IL%FC zLqfb0yyM)`6|LtM*OdN9FL9`MTeo--!UnTvA<{~2|8aiHh^vH%BjWvT=h%rE;r4$T zlEWPKU9z6`aQo(j_;!tt_B{#lZY(|yk-W(9f)Kdl#k*|%cyU;~7?XG@x@?npe;+V_ zZns}jShn2KK3*S&Fw49-4Dp$N65{7!^p|w6w30j8FT5e%9lZtM7jU|8d(-On-zhrB z7q^uE5bspC|C&-@=<8=x;(-8kP{KDy`dh!-Qh<`2m_XGQzv38gw}}=g*g4iqFxg(LTiWttR!M|3+}_ z%c^)I=e8js?)V(Gctfdw^s~|jzV2M!vG1t=U@32ELPTr5wy?#9Fjb#pHyo{W`(bf3 z;b<<*5K|KEMHl~Uxus=rFruxu_;TkgIwaoTa32`ceVj|S#rrk24|SvplB{z3mwC?p zq%*c7YF+KHb>f|bh763pBdu6x%X4bhFf$7woP?Qfeev$bbGh` z0xEf(_9Zb|45!=6O?W*d7k7o^2uQqt{rDN}i?Rqab`b7}?$N|}Ah6*g4#wDFpdDnV zcQ`0U5X(r1*`x}8vD?VapNaMKX*(F7Eh-U8n%x^QFg`mN&S|$WLJw!^k8x79UYdA0yFEEnmFvf-@3y(dxemYbc%_N7@QE5TM;;16=3UEFN zaULdP8*hNHwaFIcoQrP3f3XEe2 zNZ%OMByA8Y9K(a(po|EhAo@^jFd;b8j_xGk2!_gFIu2z*3TN7( z=HsL7A{bzM;U#WtM=c5V;5FioJ)IfLzzIfG0jbNn1MmhnwP!Pzl0?%0ib%kwV}Hd`Ks=l0u?Ih`@G&!in?aY*sQ*g#g^aP|uhU zrBt2}ONNgjVEmU4 z-K?<~)zDMGhO;EFqp5-H_+mM5+ux^qGmSw+8jluXfh^mAg&A#^ey2o{1ptWKK)r8| zL&=k*vNK9dvS(s-_TB*VGjRhq);Qso%8TIGrjn2+wh&VjXNQTbV}V4YNvUN6-Q&VY z_ek`>X>k+)7=nh!CD}!H!FFhWO6cJYxH@YNH}PW%R@GLN~A`ixVNzj)aqBw=?!n!oadDMT-;!V}P8Xbs-W^Vi33x zL=&toELCb42r|@k2y!t-F^=XZQLI9MKavQh2r)3=z*f<501b9g*b*s!AZCKOoE@W0 zY>9mkm&Dl2j$$AQ(;1zV=_bO%#UPK+43wr!+&&XglF6@Vk+SWQBV-Z9)lRY^3GmI1 z<(digL5LkK;%+u8N)ecu6lC4bgkE%}lH%*ehjB=Km}HQ*4Bc?WBsX?9YB9T5Ms_z+ z$_Bfno@n_vGS^NSawArMP)(;pz_4n79vKV}_mFx)(-BVXb^>=VOQ3a0tmw(m5Qq?x zw?u@vO-OWR9JBB~gDra;8FN1gcS88EGf+wbnT27cGf14w&eRZPiW!Kd7Q#>^xHmeJ zErFkox&!-znf9Kel~F@6Op24B1Y}?)1`z^pPeMSlL+e28Z@}q)kU|}r3@K-s48{e5 ztQQ4+qx7A+I0E)lEtMhYl^=LdX?RDUWpnH z9?q^+U`74My#{7nFFmZ!_?rJr`#rGlsys6$so!0^TK(O~Z2Pg+SxK-(UXHW z+N>Tsc)P4&_poDs`~G@Rwe+DYRjLoL{5rVg6KCoB*{{p$wmNjCc@ujY%5J$H)-!x* zo+@QFW2qIpomS4+TA=kd%*K&oA$rzl>YQtsrp6IR!=$9 zWaf(Wu`7Q1*<;)#biQKCb4YdQr*As0d)i{v+1?EvHg7TTUFotP&RLpj2DhGNn)c%I zY~{5p4-Tb&t^37%*JYPeQma*bq90SH|HBYn%J$3KB-xiO*86kP0jk} z;`vVj@vDD&)uz~^U8QH#Sl8&_)ORah?Av-~OL&-aU}gblyR(NkoUB(VVe8i&*UZ~k zTUYV^gVNPgN(8X^%Y%_#J4vimwTzqu7TI1$jD7da-5q+N$yB~~7 z>a}oswWj*mYlBN}i~abXJF??u(}uMYlofs-b)R2$rS_7dZ8`)On7;Y;AA2wFi|eq> zJcKtcD0#4yY|erAZyvpBwyePLn=SsjWiMDy+rGu!PYEkqHz>Ay>&M2M*H$Xhy&hkz zdHYX)i!KB_E6}iVLUZMxZ?5FcDrDK%eaDDlUk$#}D>HAp$yvVI){FDau2nN*yL7ab z)|PYM`CYc*;mb}FCr4gMYI5zN><`tcE6IyeBPKj;x;(@-bMC?=zg5^jXu0*^&41&$t+JKWVJt$j738Uy3#F)K>rpUh?BS69k2?PPKJxnOOupZhD@!cVe;O{jU;g~2@1SbMPG{M6ms9`K^SGt8{+CfnQ>&be z-BEb^{5dOImFo6EnHA8rVddiO7oTl2W48I3?fs2G&G${~kpHh5fsJ-{xp8M`CWtG)&4p;-dcOdud^Do zPPn`1NWIK<3%Z0QVG33HvZ$to?Qv%sd3+wU2D8>zxp}8 z`A=1D9}0L<+v@u6)T#{=OAhLztzF)Ar1hv@%1-Xl!=c~gu5tD)_gJ26t>3zT;CZ8} zvDKBg=&IzrnY~VCU--N8h~{?pi8WkMhmv1ix%6;PkL8ovlwZF2=aI9E-TQFHFd!km z!9(5n=%M-QH2?M6x|3b?8cZJDwZWv(<4bBk3{Hq_7~K3u%Hi=*aeuUqH=p0u;K$zd543P>_^j_W|H(%EMlc&O}zC*8MwMn>jWA(HvNlQ98erz?MRaSe`O7~dp zyYj>8O&^^)_Ks5D{8))o6UH2Se|x!T=f;mWDAqS_(=mLzwdku+k=-^-&a#7=&FZn^ebK+RTIO#JIh``Ef5El0CC2?W zv`zk~tFIajebTpHl_&K!b$_^cy`n)S)s8oRwtM_lp`ba}Pyg2J+`W>@KAkH5uwd1x zw_jKN^!MMdvf`IbDWAG?b9kw!%I^Aqt(yC8>+|=URr;H0GcRB5x5->C?E31bum6a+ zIIsH230ul^jasEnyi{SyfA>xAl^8rdZ_=h0zfN59HhxagJ{L1wO|~SwU#~9Qq>HRs zD{k$99<5uNODTRYFmU(ReV3Fe`t7UWxzn>Av>5WwGTHF@%{nzHeYKXX*6^1v>olD< zJt6zvoJ-m#!u1Q|sz=COKb^hV(^>yztJ>~U)n^oXIj{Thp|?_ReqrGD&R&1t_WuikSL&_jUY-?AWAq~ z-vD|K#H4i6)p70$ac(NE|Q+bruj2;=5i0ItwHY=qx$2g;$~CH2@TY z>d?YDLF@I3SAsTE8PPHCJ)lP9F}OnQrPm{mso_upY6}h;U@d3{km>MTZ3J=zT?twl zju?18F(@%eYG7&^DlZ;1f(bN&3OGZhgfIZfgFXWljrv!Kf0BeJ6%F#;2*O4VT#2ek zj}!-c0L6)Rs)72^af~p?Wc1GG0MxD^cx(B!w+7)1a6QS`M@Zj#R_mc!Uru z7d5p3gtQ!U2NDp}B+5b)%30ch#01);07;wUGf1=YWLE_*Xtr{f`HYP5ug#z-m3)BIN6uv{rE1^uaAhAHoskr1XzLby@kdmNs zspudJ@Byu*!Ma9E<z7i2h@$ z)c`00DNZBFXSfA|mBln!t$_le1rSmHgq`pUYz7AmE!b@|K%u};_%1axET_ge%XQEL z$s(NM138FPKrg(dM5_TIQ4^!vm^r-+EmGrYMnLoi)Wn$p4`N76a7d6T#Q<^8;h1V3=9~CkZW^XL#V3VsuX-hKwF-r%9eG;R}$G1lR)Z2>ZARp{1 zS3!?LwDfq!F;bbz6-px%u7)j0g~SRMf1k+^3jz1x2`&PqZD3EZs4$M-VXMl4rSzi7 z%s{asH9Tl^z(q8RG`b$@iD$7jY7}}SC$N!9P7h^4sZ~K)BqLt8q9-a`U!_w@j;TzL zRE36`R}DGAPk?eX-(*tZ0RVeaV^ZnBFK&ZI5+E7{olb?tz*D3Iy-cnGp5p;u0Zi3cP;vka*w&yX?lz)J$-p;& zwkjOY=5fG*9DxV$vymXcgeang^mAC8ED@|2AjrV(m<{C4WGUe~kPAS77#AceIZv6v zy@99{s5Q_tnH-2pO%w)GbSga%e-+R#r#BeUHI>GMegXaRT8&b!QSnBZ-k{^;2AKg0 z*rZbc0&yy}+@w&e4N6|FgeV#1Cc>8#sj?Zh>GS4m04twlC;S0Qy>mQ7cnxWlETW z2J6*w9k1h+a+wB}RBLrSuaFy+O07wx41w1u{)>(f^m46UrBLgYYQ0>qf0fDgyg{YZ z$uvr7qSm0`WO@y!=X@0?y<~EQLak7$)jX%=v__Lsu2-pKJf~M0$N(j$(#zC3onEIm zD%8AE$ElSjP9ZlL^ac&D5wr%QUae6XIgL`M)$$4*Z{YO?lTK?gDfDuUNe?05d8JWh z;sm3~pfqTeDw#nc6PpYFe~oCJpfd7WliZ}&@N$_+p-`FR`0w^BcqRVZ@^YO4%0p=~ z8r8g@&~Qo_K)2qg!~f_&BNs@PFjay*ASeY0fYPMUD8Lh5&FOSP4oZ;86c7)cAgCZk z8lzG$kYWAR}g(k9WK+D3_3w0m#I0W%B0b%AW|9w&k2H{PnaastgT4jVEjY@9fWNMQ^A%>JNU(l&cQ1M!gLCrCs zlW8;@XN1^d;=Guz)~U2wgUkf6({h|iZ6YX>1ANJG9gb7WR7yP*gR-a5}i;wWum-nK$(LH}LnD%e4{{Wcq@V}l3{1@^nm$Wp0l?Bgy_y!V8n(%L}kd_Ae zP8#&3-e*tzD

=1XDh?xH5>EDGZ*e)K-n4(Ee_r3V%w@VH_QnegoRlh5$Z05v z{Z{8!aNG&sHwV>(bO07&`;!FlXsZ{6;1^|3DZ`2DQ-Bc=t z99UZge@lL0H`bI|PA>z_lW`g&$16Z#%6L7eft5Kqa6Rycpa);{TA*5`MyJt&noy~= z0yGH+s)Lhjv&n#$Xh*MnR?rcX;S>6JW9q%HOEw z;OL@N8DzW;ywdTowZf>A@dlYfX4Jq5N>FpSK0w3_kQRD9x-7?)A>s%W1~>_T(1U(Y ze^#nhIt~^vnoOuSbr@gN^Lmw9gXjmjf?A?hQmIf&Ye9A!QHU7D+<{xzGZ1h=t%Cy4 zL#FhQ3{Ykz1xN_55TTu z1M!6(N&@PGQ$ctQD!CSMg_j#;coH#De@IX{Lr9dc4kw2b40bv01EM^MVdZ3%5ki3d zX;OmaP&WXMtX1`>lT-pCtpPAz3kpo9R}v&@7FpX$oAQ19@tMBGZ~scYr!k8&v?YavefAh#~`!fk5XFe|Vq+ zM$o_$HPiu=0sS{bO0`l6S`!^nsNftTqth2)A80`toS?OYqi7#`gxqKVNH^+`y0jQj zOu9yA#O4D`5EMLz8c5G*lej>SSd5w z6c9kxkWfx0h$OF-!&U~AbxM?~$Vr?5e+{C82!i!oEjYc%*zZdqeFiWKa8D(pDnu_i z9%)UfHzBwyjA|nu1CSgbtVpRc01c>NC^jfXP}Be#CKHkY6e6AK9$7)oZkQr1se_8+#lzFJL)1a5I_T>n*GCYa^(80;yUHvov zbh#W8sNhWkkuAUyj8VWO4tPgT0>=pAP6L`lp$2IKbpy%MnY01|C#-1za^tWrw8%v& zUMyEH(lW8jT>jL4qO?QkOzg1A*f{0C;i`Zj3<<&{F~se+^WhQVv_IO@Nzf zlnVgtP)SJYT9A51BS8lcgiH%G4FiA!sM*p?kb4u)$&4@<BAm5-@$~lmxMo_;x0r69V(#D{b zfhh{)8>0zK(P~U4f0TMUG!AmA;-(aN?avBVv9T5Q8KUq*v13;2^IV=b^t1)db0o5QpkplyB(EdnoaBL)H zhnxvDj&FK`3UJFy0AL1?hTKR3Oa+H>Ksc>|L(L5*cQt4Le^AS)V@)LOO~`G4U%-)o zn~-Fz7KZj3428<5Ktuxl2xkSzBeba&PaPmHL*dK{cTiz5Ld8=FGy)c(RtYr-#125f z5i|g)Lx1Ub9yt(2jS1r`GpJ}Gly!P)V4V^r0GO7ngYMxl?Q-ZItsWGJQH$LJM68^H zy`{t7Ehvz4e<0K#}G{f1Z|nY0K5P`m;e{Bv=B7`>>(0BjG|oTH7X=MfNMPutO>*d z5y=OkdKDG`1kGTO%dmvC$ohbY_)7^ekAwbG1Bb~_=nyKh(~bsC z9wuZke~<#`p9%lh1bQtZ5wbU>Kq^!Ig}Y{YZwo53{Vny7s%!+w7iKo!tMYw0{(*wlqaN~ zKq&)QaB%D=$pNjah9*J*uUF##WJ3kWX#!cQg1HD6&^>@6kTE%i&_oR-Xw=wHa+S&e ze@77x+Y5^jQUU1#G>~H^)kY(PR>>h*0FR)EGe83)9D#xYS%~Ksl>ij0RieR+V)2Gd zp%Gve4Tx^gmm1E9fUE_5se$0=IUpp^mpuI7>BpqPLkO4#5|Beh2QaENDM9kUQDttqKkie;l?g2q+`8yBrk}b}K{=irS=M)GUJnR+CcK zMn*=az@NwzddDxE`00E1e&QG|k_L-o>61(OFS1k|AIUNC&Pl6s;?DR8mm>`y8R?Bz zq-cyZ9*2^hG+O*CnU@lo;kDf%~U0U6T+hz3Qb`V&tc~mjynGzMMyLd zEtX0^Z0(ek@NlaYFw|-tY_(eP3%=kZ)GEHh>1LR8KaAeP#lg}9c!7aze*}9C7MBQ> zROh=TtL;c$~(hWf1%9_h8pZ)36|=Z4_| z{X+)dYM+@UO$i4)9*ozJF?8gBJg5G;!+8B9xqkd!;@-AcWUz9Bf;R*x% zmpWB?XYutnBiy@3c+M59fHGpkvIl@({{DnZD}mJZl}ThSQ~a+%Df?|6ZuOal+ps0V zq>}RmG)FK+b3tQUe+gZfCc>Lx(g5!cYF5u)goBz*p$DO2vG_X->deZP!OqT65MHm~ zppiRrq2By>ALN20Umy7+VK`MTTmVyiq3FYNjQ3x|e+fUNr*w72)z&e>|}+dg6-k#K05!0-q5U3p|Ap7WUNT@wj5?(`AXFb>M~tej+@yD7?91 z@XZ4YT41&Z)`neS-2hx8LY#|3EHoT1A}q`ZW|hUo?t9SI2(ksPJh&1c!>phwu(~S% z*YNNz9u)7o(kC*;Ev0MnoAl)x(43v<|Le;aSOGta+ZMxx`qzB6u>P3@#0+=27y=$W z@nEUL5VS2Iz!TwN5r93#z_0|cAYvTh32uwSf3V&xtXE(R^%Hdi7IzueSTuw>3Jgx5 zW_5{v;qw4K)Irz!Od2k`Qg%DMglrE`R&^}s**=NCGK>Iw^yJOs0}D#Qqdp%}~rXy^bB;hIadAUvF^#>M=?NTbfeYDdPx+{7@4pnQGp z>!)dcgT{sW#pKiO5memXQ7(+31&<~!e`z|V+k)60K}q7F{auh+a@LB$6oJWteuPI@ zSSV=uFj@wK4hoNDXcDORjfaj zDOkorLAOx75h7c~oxmM0M236Nr|@t9AleLBkCp>WV}}k4Rbm9e9QBq3iwcO%e}aHY zxEChkX&JTwIziyhM}&#tf>$JlGE8(BF6cPp!p4va&B9nnL|#A%>SZiDqE-v@W)5Z_ ztq|E8Zn+{ciC~|{LV`$)ZaA|>RBWPv_aMb#k74>KU6^+lm77KE9t$fIln*YKl4lP^ z->91{V0#(b3mXM52sJ$}dJ4&Bf93A-kkb*MV;N;xS$3(BsS{boG2v_)jr6b#6KCV3 zP;65yH-=^uVa#WQSaF2QBBxm-j(~ZPA(2I5T&U&H#7NN%5B*T~sCWUYF?%s0aUft) zv15cO`Q)hxjS)1*8j1_?tcYn-vEkC#2bN0=RYMFL&vqqy0mZQ0>qd~hf4s+w>DV%~ zAfR#tQ$Z0H7I#Vt(I;zNSQIr1en5?Z>mI4CJS27))Aq*}LCHd)vq;es=+}o}5vUD| z$K^$95?fG7B6LKAi><|;TR~<<5Q>TDgqC14MNlU)HEv}c$2wLb9HKx_jZhITk}6?@ zA?P07A%`TH=E1@VU_qdgeMY(CQh+u6Yk>;|*O4J`C78~+2qYoG3 z3FeYmj2L*HiTiMRDwZdsHVT}@MR4kZpjlZndg#+4nr^iM%tuhC)0#2ZFkvVd(aE|I zF0ndYtc|hzKvE$s;r@}-7(g9VwJg6L6#3{KstsC(u`XlwFvf;4e-^Q^A}~5G1R!yT z2oZo>l$8i7U$j+YVE}EBoMNp`QqtGqSRo|ANPI9P^Ql1yE1U(u1Pf5Fg`HmkZCoOE zxv`X!GMAI%@R(^m6@eMU_c&3S%$cPSJZZxDRhBEPdHqmG_G(q1R6n|5y7lR`~v^sgP4s1OSFck1h4l5EhubZaptZlPa>%B zk&Rf>0zFwQEHfkq*gSwOw1}8LhOwh~%JPK8x=1$o&OG71M3l3XVMh(S#AAQrk}8DP>d z2v{sja0Kg61afRcM1K#@D2*v-rUt?zM8dH!TSCQSB9S#49ZS25hDI>j_h5^QNJD{! z8Wqn6E;5YyeSP@vFBvd0KM3G8}>q6PXz!VN^v^Ii0;4{-~L@um%7=SR) zL8b~r@bO40n@9xwiw;YihIv06mCzyc@$*bol_9;R?<6$Ulbf6pw7i0P1XhJHQ@MD}LHD4z9r z=9L`xA2+B{5YSR>eZ z%tPH78RlYIlh_$L5w)KPOsHl^86gvk`p!iM448m`&Ga2KON7AeKpO;v6jo`1fQ^Rl z5mADGfANJDg~Ad9e29$;L*a=agd^0XC9o{H1ngmf>3eAPSOL)qm$3kLA=09Kv<9pS zCgBtTGl*;956N{%C@u|F33!Nr@hJj!Ax^QteI6T15O}7bA=`rQg++!R9xH7ikYc1Q zA@>4%NPF{9xFbX#fgSt+Ql|N?aDgEZzA=;Fe-;v}=sY7Y9O**JD+9|!2`~bBNI+i& z>|KF~DIDftIg&S!HvAM#U`sGlAQ4ew{O|^r77l|&B3x*s)f=!V=42F_9F8g&28Y2D zih;k7;iD|?4h31?A+dKRdy1t=#JTvSd1(F-4e&1Q+9 ze>p-Jn}D^0rU>YPfJ7^>L`8vS1DhyOU@_}*i-kFZDMWU~=N>^EMkYbEGn*)CFkZll z5~~AWUot&3X6$c8)d?sq za0Mt^mUJp&^dH?02f(q2I+Z6Qu;dw6q!AP%uR~8WP=cwfC{u8Ct4k^+WI3smteD%n z#By+05s(VVCaOw{g@G0f5CjIPe{4TNsvNcs;xNU`f+#`qoSH_U&2J3HLQR zu0eeSd?enIK;ME$5+-6HHIcX>RIk)^VhMVeBa(GdRKh7-B&g6o9$>;Z&c(_eBZ*8- z#YA02WdV;OSbEWVL`?yz%I7Q7qXHF61i8n6F_KNN20+jwi5vvppP#x4uXfpjlu(>8)wE!5G4AJ#-c!BW#Qu(6iH|o0TVM_ zKn@lQ(IXBcZH7eT0?2ZC@e88Gzy~&B;*`u`%h0zdF_*lE<>)h$uXqd)fJmcQ453e1 z2%^YpRw7Y=N`ln6SUdC(f2kP%$uh0y@`Zyr3wr zS^{VsgYcBV;_pkWSUVEy3Vyd--tvY* zvEYtu0#JiDa?nc_CWD5Z)e-Ec*m&4(}lZ-&Whs9+bjw}x)*pcBtk?*lO95BIwXp;fnIpCqg z0nkKGp|I1)ELaC_S~DCdAv2&%U=}bLOvSo)I5IMD5%?706zj)uFKxDhM^p`@cF86r3kk}UMaYR!W7bg&3SvhH;`GRP}jf0E!5#<5n8vO0jcX$dd} zEP=IQ)eHw(f{S1%)X4u1oXBp&$_^lToRt9+Jm^2#?-J3}fgGRVp?xSnE(fZ%40!Bt zFl{TOP@7`RG1jy{*(}4aR+0VTo*F3_*cOWeHx}rz0|SS}$acp>K&~?AGy_8r&2T6J z2SpmJbKsK*e>%%hp*Uzmcn}qZjzV-Y1W|UN8y=QEs6YGxu7QYwsYsa=@eEkRfoBy2 z1*#BgFy7@5s~01OyowVY7&Hf#8RZT}qZ(tEm|&L!DS^I6(FL{!Zy`UUgbas^wXOpo zGlR@?VB+AO18vX1(};uB1v&xl(wWI2cvJY&K`jQdf61Vsxlo5_DGbU07;SIQ{`fc#0OxC1KI;GuyScZ7>KzI$B1NM zSRp44v<-H*M4@hEFp!*_C8{cL$$@GpL(C7rf3*1fC`N?RX83xE?BZar3_7klSc*X- zVQJvChlCd9BO?PuD79hWXD@8%Tz5oa@Y&GCpU=*Hmt-fqWI7wR|F4sLFJRu!$DtGl z?HEW>{7X~6uo%*>_mOlC@II}!y8Xnq%1uw!r)6%tbtCHsRnn3%=WAs@SJ$3vsj+!g ze`(ndn+tY5CC@l?;d%Amt^$+39a?B>iycE<7d8zjaNu2)zu)eC&`kHqGIe*I62xhUP&+YTI>+kaZkws|&7k;YubJ^EL)~FZPt#YfF z>`2_t4|lcNUt;f_k>eZd`^*cjHokpm#c$hxm|OQ=|JNb;77g39ImnnN^shn%f484| zHlg-_?FXhl%5y#WNuH+lD_!4NvNc!v&9F;1o^?%Iy!f)R)OUZ+X?<@;!wwzmKAB&7 zP1K}&t(#9x8R1A?+;)AXL+1v!D|Pwrb2Yoy>t3~a%Y5t2#>5l$0b}Grn<`Z*ePrCx zZ+2}cI^x@s9m9(aKUFT@`*+_Mf3Al%8T_f#X=Ul}0t<24eTCIA)@qv5cUQJQRKM1i zVy|~Uo|<>nw+%+WF1GymkFBB$S61#UuyM-6YZZG|?(y_!?ehDYgz3gC>GnYN;HT#! zg?*iubh;lsrFc@xfW%oV+*xVM-@k9Ayf9&pV|ncGPtNO$JHBbSyn$}xf3{^Wk905K z{J41O=4HQ+uTv+x;O&;nN4+hKTEvPGZu-(8!zcyGbkjob;ZTfX}= z6vZsgqwU0Xw-4Qp zztPT})vjyyh?19T{&To&f88|&-WS>ZK%s`nluxPybF{*7#h--`juc`6+y?Ct=91%d>voROM{@Hid+?e=VEM|1{5Xq?EJs zmOI-+tG!G+vv@<%p0}PiZ8rZ_cJ+qY_ipd)I(+2GOW*7qSM*rX8Qa(F+rMjZY{v!% z*3Ityqp9S!Q8WKeUtVS8&K>JC@1Hr#SL;3N{MKzum-lQRBdKN%LytE-++_k6<} zXJ=0=AGhDW>weh&=N(=&_+gQ|UZsRvLw+jNqhICcofj59I7)W(K$~?1d*2*VBK1OY z@5Wo}ms@mqc9&s8rj*NHGOsE5NaB`-^E(f}b7gj|tp}g)f877DbB8E{V&?L}mD^Pg=~Fm-z>hmC z6s-5x*)?f0%mMsDQw~o@Fjya&PR#A>*^k%s6=L=yzA9=2@7~acZ%}v)|t6roJ=2 z-?QiIPc^xHd+D5$+x7F@2MeaeyxrSj;hi3_{P=evmQDXuJl^K?Vy@%TStsq)gR>`X z=(r<);azR>_03GoZ1Ak3#yx9Gh$7(1$J@th{*!pVe?zMd1%KJm{Qalx+Z#8uHTuVR zsA}1r>lTf9JFLOYsw=F`?*`rKHmKG|l_k2@pbX9GGuKX)e09C`?)9#_Zw%~cW>bkct9URX2F+R)PjZvQpn(S#NqZhd1N*|1yjQkM?j?z?Si-KRThXOszB z6QS-{f1u9%$8Q@Ju3NF|>t6Yui#!Xwk`@Nc<}deU+<~-NpG6^M^g72!Ol?k z@xQV!_OCw6Qg2pdftpuee*Iz1s*JoBe#)Lzc4AcEy(<&u#4YHvI^*5nB?9gbKiTc? z%sI{17kb$CbnLeGj$_YEZ}Sy>x8z{P&9uwIfA_SncW?B#$%T_<4qGBWI^Wpy-jwVW zcMgyL=XKdhYaEID!j@*9-PAGoRLamYBY$YMVED?L-<7r<&0nL}6vZD0rtY88q5S<5 zY5)8%=JLMAx2yKJ=ebz>cnweC-G+ws-kqOwJYvS>xY&HFH_d8RE`LV&lC(C5p0-=q ze<`BIoTkGHUFtJ#QFGn*ue#Q791WOJXza$>?WUHAJp69ziWQzE>YvXoRh)eBrtyBI zU~_+Z?daY$tK5sZyR)&A>o5Eud1vd_SH53Tc36eY`w~~4Y5Tfl z?en^^p@GH5#oSHOpBWsOx7WSa0Z%^-(Ir?sq#*LZwu+$`cSXN4fnP>bAIBX zyEpd*Jg%{=UEdMQpS0Px`f%?SfBlYxpI!27c+Z}lYZTfWerA2%V}&9LR-VjGe17t?37$M#S|`UuUYL}B==VFu9MLzgdTa6e>AgqkN(udYaE@o+Ms;l5e?($d z#=Je}V%MzRJA%8EZ7VVN?jJWh+kc4->*Q#@`0Tk(ob8{2#~UoKdB0NMf8g-PD__5F z_R)D{27=5eXDdt^5_{cgq&V_UZ@@Rk1Rih|X&cZL`5R{n0% zvS;b<&KZUj$&xSU+Ke7)f0s@opqk26TWmI1ND9ZMxoTXIf zBBz#@(_FR2DKCsFRr0GQhtI6L|HM*yN82G|OIC^*)oH+KdF87AjN0jJan5qOW7Ee& zUeEft*zva6j_yV6-|tJDR(8N)k$EI;{?sVBksMvy_~B?_ zSG~TqJI@~Y=d=+$UbbY~Y$%^(34S%yKv0e0V!@l>r9Vce}^s&v17I~(BTD-UCcXcmjPklXXd7EX) zCsX#+Iy3cgQ$>x<9ols(x8Qoug2T1d_sl<&s9iZPWG&hZomgn~tHkMNPJHXAx^jGc zOHOsP?yU8;V|l=;-zIU@1${iWd*4$tnHzW(K`+VfUf-qS6gn{bCIe|qSrxrvWNAQe}89gJ#fy&tZ~x|Os>yl%Z+c< zqc0j(3)4Hjdb**qV*xjSWYCEO*jZr2P0dXYduEEiAksHcSJzXJx`Cb#JU>mSJ zsxHsS{uDodT0qkBhvokI?kijJ)Z67>Oh?^aI`Z2&c)}S`wbsYPk04CKCRZ#mDsOx^ zqC%zKe;qeIoqyz0Wa=cnvS^bnXG3bAE|%x+j;_~T%Vw7Q0qw^|DPE`PUmKTIAIrDA zv8w!`ZKjds-(8+{;aKsT4>Z4zWERi3F`#y-;(3ZsW)^o*7p>d;WUZzV1HIBDE_m9O z%?}sVZ<_G3YO6=#*eC@pZD)UN{$Oit?-)8Le{XPf$01LfeCTP}kyv-v>g&<6JK42W zM{6!=v?|q6?T1am$oK9Uv$rfPRd38X+537EPY*4gFn#LW8un@bd{}B3S9IQ~KXzw+ zJpO0!#TM7TB41`!oL1}d!t2L2_5XJ96Gha}VFNF9%QNtgKc5L{{W=wCHh4V%aL}f; ze>)1>GRnP)9GbsIRD8J^+nXLadTIN(s@M5JdxF=NFplXPs7-O-+(`h`D>`q+oGV*n zC*H%LDARwwbE9FiJtel^S8cgvIXWwNSFay?Xa46n+6n^ z>Zk9vF#NoH(RFQ;e)F>VoA-Ay>^pay+g!V9KU?hX-j&Za+VE&Fs`TQ6hg@y{=XVW1 zoE=jXH0z%agczpMn?&4eHv887v^w7C|Qo!#fBe=|W% zU;V!Fc6_%(0}~p2wP?xo@XLmlqFnIy59|0zy7Lzw?*gLo4>I)SAVh@?+BQU^#`7uxiG!& z3YR+lO?G`e-ngsgX;`P@!@M!8f84tUHVim6wp#6sPa+76+js1Qr{Srszx~lP;nv>| z{~ZK0EgJ3Fbtb;hPhR29A0=rC}_pM7hVcjQ})wy18kb3Pr=sMP9p2@MMVvO26yOv4D{ zt}9a~ZmW9ZP@VPtZocWBuU%;Vz$Nd$kKeUGS8#v9bq{cO_|;P~_s7P8&o;cmMvOAtCP{l||0&J@HUnfz(c&%I=n8y6#4=quYvatoo5G zdo*T3WL*84DgE4U%=t`(}X#^DakqfBwzoBfALa&C1Ij zo&5V!&4m8Vmev&ox(8(LpIesf{=1=Ze8;;>JGLKNZPb2{xi2-HW|>*pT~69=5Jw}% zJa+=8Dm$gUy*Ov$tiAUfwzl_*tsQXfyZCPHG9SHFBrRM~@2n@aMcY@Q%hQ`*{#2{Z zf_|y1Iu+L6-MQCre|AXI5p!#+V`6tSUHf5I^=D0&AN|?+@$sDL>GzlHJehaT+$z78 zJ6YsL-skUP2Og83{Qby)y;bgvdeC!GjRKp#(k&f)H?^t0#|dMv;u$sfUI@Dq8T|V2 zplzjw&uTQc?cKQXx6+NfCr(?m!g0IwvRkuSs0V#0Z%KDQf0%tjx%NQn4?`zqKke{G zx$iqAKB~~`bn#c`yZ5(#ETh-d+*Zk>`m2v)^tm@3UyrTXzS`Kp-yIub$NX(S9y+d8 zp+w`E%|j>63hdG*=;r);Jx9+US}^HcqXFs|S7zGrV!I#x-eEw3C%*Lcn)CZL>XPPI z)O^VF@_ptlf7PrSw)$exun~=atGhe0WSJs0o}`pMexyR+972 zv&>#=;nNjIxD=cCIoH*ZGmhU|+N^R)H>a?8K=I(1e-j~FzL$+X7x&M+w1UCgw@$5- z=UMWC{ugQ#DmHTa*2Q&J9v$|6)_qs{pzAe0O?wlpx)s#0pE0;|PySVxyaQX6Yxl!? z*Y=s$5C607!P-H+TWLCnHk{VrwXWCxE<#qN@#`kMjJjWAW3lU##XCSrTTur`^%JF zs^9W;S{MHIm{VQHdUer-w-tH?*Qi~7b^GOK?{i0<@LMjA>S|9Y`07D$rRodno?L%> z-YVIYv2P!gt=Zx7)3%;DEix*c-TCQC)#|m{e;gV6FsVb8Ps4u*yd98QzsYRXy_7k| zkeK~t!tx2#P9zO{v3d05&UJQ%S8v&6@$WsaHyD_pDYAb1s-zb-+AuHmMUXUkKU#I_=0b7VA#X4ic6gq{?g>uXy=&N?B3lD zOz4okKDqF_?rNKRnc?J%O`{)HYBeCG{*36kOIB??^z%Voe`T1 zhYxkV9a%GL)Vf+9H*V9_8eA%MFw{rqf1&zPvL5&DOn7l1pQ7id=iM7$uiD!7=wA45 zdl$bw+~Vjs%j@9veez`ttJASj_u~88c7MNbj!t`aTV8E*_l2Ps<>UL83+JcgOB!*d zd7kgw^0cizi-eaOb@tNZXOmwQ?i*60!N~mGzP*>atZBl|z5`OXF8-=rqaT}=f9rdF z#kYCRKAX_3<*C{I)h7{A2j)JT&;n>t_pXwwC}oE zwAQ`vmqlOwL6$$|z|r@z0pET5f8Fze2Nt)Ds8D?LsT*|;^($M~?)quWt(Sjp46O9r z>WR~TTz@>F*V*TTruABp9I*6V$3EsR@~=mJd%dat`=6$odvx!4A@6S2SIxglDb&Bu zxn8&TKR0y0xujwA!C!u^VmjC+GO*6hUoS2_HoV8}I^%CE&eUDLXIuyCfAkfv$2?fs zLffq0rv~O(e7Irx zJ;$CeUj3n6*(FWye_!)%o)--(J=d2?o72fvVffDP=b!1h^i}>phWs(LdRW}v-!~WG z9*p_A__xnWZut;8^4?2@f1$XrCScF1d=oab{HJq|fuEi%Yw_pvo69$b_NX=Q?A;lC z_UJlnFH-$M#euuB3Jc$ysH+JqKcZ~F;fGZhv|qgA;nZ8-UQg&yyvlq|FSVfuU9 z*QfT*=y1Hi7+v1S$y0uL(cy=g?;;1#omFPoyN1Cq8+&wkT0gw}h0tn_SP_sN@Pj~ty{ z!g8+nk>_2K!m2bmUS;>qzD*ND3-5~g#6K^vZo?n*dweQA$8&aEQ%yBqZ)&=+L`SZ` zPy4o~-I*S+UqB_9(;QBfZ@R-nv{D#qU`xQ=K_NBo=m<|e`&q`lqpgeH2wIB#aCDU z*>J$a)H{Eyy-<64r9DAGfHFZrz^Oq&LG0`j1l@z71fl_+LCqKx1UeZZ9;hY=un|HZ z1SgjukoizuK~P;m;tMr8>}Mwf0*143Ie$g%oh{{-~-i(xo+Uw4)ia)gBq1?p!9z@0kMi7qMoN&s0M?e{@@ZbN%w&D#0{y_gP>T05Ep~6(=oi5 z!k{1sFOC7WV}r4<2`ZqlrGT+hj`$7F2P`(w8<^%BQ?CNh@Fr2cmCc3!7~ad$Wyo za-!Lc5>#RbQszKPDAy*T9^0A~XtrgUS%um_H)0a9%$a60D4mw(&np@%OQb9X+7fMI zMYDoVfBXU%c#j{6WQZB-&}_rXwwbq@nRv^x0V5+(Vq zN&Ni7j0$wC83eA)Y(p%RLIktG)sY$Y zF-yrwl#EPdd=r?JyZe!;ZDw;I8iLYI1VBIaehae)D%`LM;a7Y9Z z29nbpxD^5osK_LznerjZHkN!FqTTi9l;i6Xl>i?94=4VpgEdEN(_cV$La^Z4%~Ynb}H`c((bmw8U}}$>nBr ze=ukZ90bhb@xW|jKAO#6V%$XN2gbN&8>#?JK(fCxU|dPF2V$)xinUAwm`G@uz)83mVb&IS`P(6qF39}twa_;QF)78U^e^O z+?<6Qu%?y>G*JwtS=_^BhN8rFPeesLi8?b2L9(UDA2yp;qLN;PN(l7dpSHJ|HHkK} zbg~c`o|U!VDaUNfl?(GE>68O)Y4(=3S;7%K#h5X|UcF()W=>4BF~JFOOB4mCh#(BO zwtv8C<}Bn}5qivKreWZ2A{<{PnX}9-Ng_+wlSL^p`|6GwD3Ojokl#eo9cG3>W_J7u zL=HpL4%`}OmXvOy9}nfKG_#G#}x43$*e>cDs!Sa5Pw<WsW%-qt}fLtd@2^)dd&!uY-FaXscN#vJLR%RtaD|lPW@34V2 zfhaCfg=jt8YKeB)h^w-^)CR?X)j&;5$_9opF9L0n_)j#m!;Vk2u3bO&`XQyJ_GkxOyGdw(uO%R&E% zOtWM1p)S14EfJ$xxNsNLd!@q9XEy#<`JC#^t$Yx${X5x7oeZnz)^4=rXAF&|F#h^K=Ivax?UY`re#vJ} zvimG0c;%DwPKpOg5;i6$eSbQg$mCAqbU&!0kvSv_c=iQ4AS`y~Z0CRRz$w)Wt&Mn- z3i)<^xtB1IUo;e7Uy%I&a-T{7?!(8wOKWdbog-4IJG{HHAhTzPHt_L3;!z5-$brt( zRHvVOcal*U%ztS!sc@Z6R&^|AxsAdR{<_S++r=Dzi6cZm5hd7Fnt#(t3f}4bcTwrg zExLVWNIT`0-}Fc#F1!{eJO8zonZvjXyTyX+bb5Q$>3^6Jw-%coE+hexDlYihD14Bk zvm{0D%)xn1XHH%0bV|AyGx{G66ke;HERdc6Ygr|@#t8WN1t zxzcMfDiVn>oPVjTzMMoDVvxk}lk;r$XIW*n4i`Bb#^=j?42Y4;f$zScv7L^wum6tk z2-<$?Bh5P<-~2%Y1D2cLzN{-~?tjKjaSsO1aLF?7Uq5N7R3+5t5~<+N7Z@Hd{Mg;E z{k?Qg(;-03L@~_MnI#+)yQLUls&3E;>mTR`9^_d^XF6x|F zXU*k9&woohKUC{BW}~O$S3mx2Yc@CCU1dx4zC#m*a^GKXcly+Zk|)!+L6sDv)>x(n zU$I8SX$Uww!PWq(%5jK+I9&nObI@n+YVwM#8nGi}1* zxRI5LwC~1+T={h1!~eyumw-o6ZChuk44t7v2@)dGOpz%_5Cj=*MF<*HkZ=@4AQUni zx=0wbxpwxwGo^D;~Z1!YK80lcu(Vd8_95J@9y9^P+B@i}xkf3AIJP z((^$2GTrpd>n*>2IR3=Q+SOi(**>C6=b`}vQ>q@#m@#MmApOnINnOqx-za(cU$a6T z!-`hV?$ET#xCKwO>on>}+tsrjJN^3KZhzF^(#2)N!jBcso%$PFxvO^HZ#RZCdG6Ay zCv)@Rk60d3&+L1xMpBh{TBmj<%Ui? zH}>+sI&AU}x2&E!zwqV}C#!dPe@OB#bLZ_p^7OMkHeRnYWd3)dudfaI<*h|Csvd7# zd)meJhDA+sHtvn=($HA*)dwFP@qg>eb_d=acqg#l*_YKp4Nh5V)I9QT$(jBwuc@u} z8h|VyN0u0RofoW|AD68-u3Vf@|m_(ukoYT34p=Bf$ zzd(7x6$<#1F}%TKG6UogB~_7eLsFQk@WYQu0q~d#VlO);{ z6;&l^V??dY1o%sIW>5fs8BMN|E#M7|Ugz^sV@fJiH@}Sp)ivM|RQCc(L99vkNR^oA zW(j|K+QBPupWqtA%w$IXrhms|N@t!z0~k>fo=XzmOJfqlSpX%#Sw&_*gq_juWjtdr z#0E)LA#t)@A(+Fm$;`kcJJL%KFfhh2HB(hPV|E#NP>IwD+)#LlDGWR#OW=D3XATjA z3nFsC-eKBZQR7ZN1FB?rKoy_^$5G7?$3!ZFLaisMz`exKdNM77u76b>yi$3R9ilv! zD9p|auWBX4uPR6gkzsjNf}9v&s>o5vc!3%KJCtQIM=}_N3@HQKtBfohLtm?4FzhuA z)JBnRD{&e_%2}EM z1FC{ry`4)f`yNPjE$xKoYPVY>CduREmW)D@C-~q5G0I4$GQm8iJ z87r9>C-EMWnXF)+#Jm#F&QKEipDK~{z^_cf8AZnb?EHVSWKs|-%%Pne2!D+c|IkRx z4$V{;+^+&N4rZ1l;$_gXB#{e(;-agWJxu{$Dv%1;0*C}txPJ=mHZX{$Lcw4q{QcT`mp_*=&E!K1Qdc!B_ALaFgP!|{NKfGWhhOC>Sm z=1@RQktGDKs(;*ukqJYR_@2nrNU%>F;3}itGNcw0A#M}n4t%!+r=#Lr!;2yV&<0S5 ziml7kLu#4|`c`L1#7%bC7T`!?Dky^v4~ju$G7OSQmY^;wC5G8msBi%f7tU3JvY>Yv z4^*Op6Nm5|5j6PiycsfS5;+T1<&i3&V(<(1>g$ac4Tabaxsl6C>~hmme{~I!{EL# z9cG4W&$ko4s8{Ty!9WQJq?#guE5U2v=Z)9FvH5BNlaOd@@dJY~5i1_haMr4jquoHli zNf23T9T{7sluKwE0G#p(*(DM?KjwmF+6~Hr?2%QSjq%{=dlmMke zXv`3&f=)T_q|wSqCZGlU!n`o}R#i%az9~dcbf_S&j$X-uIZ{-ZqkJfmfioX$Fk=9# z%zsRPA=In#5faUU?gltMF9a_PA?L@Xaai?G#CuN zgdW9AcxFN;L6DIa@*_aQMj3v|4!4Rz;29pFmr#ZOPms9DxXB)0w&@9NMcRsNu+tAO3s&oOH8o8=oi8YGk@3# z1q3Uy2`Z5VIF(=p`z3+c6|x!dRuB!qR)(0`DGJF13nGy{LREk>MzRXAhYt3job5&w zWJrk_WI~6U4}in!GinI~fdl(saYTOfmm-6cRG2BeM>d*(DZ#vA6+2klAdx?_8(_e} znk2>KN=y>k4Ll6}a0)zyYO+gE z5(&nX63yUf>siK6crZqS59dx(yrt;|J#73xx=5pZy$4uMbboH%Qhxl%2Sd_G5^p#F z>U2s0h@Q?IzSN6GE6_5@`y02!4(TMJsU1|B6AGlvB_A*fbig6`AX`Z8O8_4EAsHeC z6#`2A&jnO#8G(Au&QOFLZT##2ED3|Uep-Cs=LoR_zEKpOWE$|L!n^P+`i2i6FMmaq(9Em7HVP$i6(!?JHi;VF#ZN-C zxi(U%31z(0V0W!;cnZn=a1%)f9dQ_;)12_3lv&qd1Z70+g&E{N-6XzN;&PaUDpG1G zM$#~Pf$x&n8x5w(AJ4NYnuCZBzKrqjmV0npMz`q&s-@_j%L<7%Z(T_cl^fvv5|qoQ z1AlZtN1h{yB088hCg#ecNs0c5d+9%|Z}~j1KKPNvgLl1wNwRo;OPiub4=;La%VCAt#c;{aU(U-xh9YfSK z71ke5?Tmi|S&`EaeE=$=vn9@~Gb9sD#MC%D=1{n6_H`PP!WqRx*dWJaP(3ikdw*#R zCw~5o2rEl96=@L0nLLLF*+4$MLWhN^DmjrT{QM}1Lk*&*Z+9dUAZ`$}BT=jhDUb{$ zHgJ1npe@n{B&JcmE430WOiD(?swiZ5XaROH=rF znFf(^6^@V?61g=I(|b}62O#sW80aqpeGNz`5rhr9M9~0rND5)+h;|g9Pu@xP$}z)fB;RXNFRJ84rnLMwsq@(~-*C?& zjR!nU;~gYTTtO%<2B<^~E9Ph~t@Ye=eTyi0xR-YoF`^dK-{o3-d&sV;`d;wTh%lKF(*Z+LfrTW4MAbM4eFdmNc31Wyc=d&V+C>tzx6cf}^zqi5yZ zSDCoV@J8hl&Hv8yin06=g`Sus#Ur7blF&06mwg6W_J2}RQc6M!LfVc;N%*5n!sFy0 z6H<;P0CY*IG%_P&eFEl4z>{ONR!UMvYC_U5j6PVBWKHdul%156nt?U;kzm#bU^Dpo z+1rznX~OL1@vAlY)%CR5jO?V;;v`qt^^<75?b%5Qq*1WY>#M)M{#X+2q`H;rNsnKr zU|b0(1%Fn~;Ac{C^OYK4*?JuuAZ$H#BHhd8sY@uzICd;MqZpqPQZu$Emtvc#))cXk zlzqubR{Si^E>57|i?%1A(@XJTU$$=v>L-<2&m^bnlGm5+OQ2`Tlmv<^;SA5MNNvLF zG&b4wR%!xqezrArLIO&%9(OWQ&XlHB-$T5#xc}46(5@8&r1n>bJep`6B2YCIRH|VGq%%82k|)}6>ux5PB42FILk;# z<;BlPelk0ip8(b;L)$YFGD6MbX*WdFNGHcwSY@8g)$L^sMFf@2hkyMA z`+t>To-hsDz!rxz*D&EDB000t5~0DG+Y0eGks@Aj53f}~XximNeqMp=TwYs@s>Bfm zMNkfld~}>1^Md+}5L>CNhynU=jZ$^g^%dB~;G!aL0n-IU`i|+d&?iK8lbU~pMMxkh$D^-6NuxQ9Z^)4lmoqztq~5rBuQ>l z9uioI3Wa+4=yD16F9WGaZ6P;{YFKfxs6MpFojs5Ihf-YxYIOW9etHQH3iCO=cn&(A zdpPJ&bKWDlh4ajZM}~`+eU_yn1-YixYoDU{Pd9!73EI3)$P)dVhqTZAN)-H;pMS?C zMQXXO0P#`O!y-=tiQW(rRRp1j`-keKN)alMr*&o#{R0Cpy8DXI7>F*7)$h8AU@D(m zCo&^NJ#|GM!(7& z+_;%4h;O{YRtkvJ&J1EJ_hw36GJh}X$=fj~gl|Zv(>4<3^6{EUB17jN5Wxk8@~iGdw>bhNZ3z&I{~ea z3KB!Y#a`UT$))c)ao$1sLVrSNnoPGeu^^FEuUA6!K|T(~Pp`T>UlcGM87{iYFEm1P z{N*~!Px4|me;0Rz@{@t9INtokL8&!tS$;wRgS{Ik)a4ArT+9~{5K!cXMC9KXXdnNq z=lSt>VJSlA?XZP}@Q*<{-co@=GyF7K(i4O0OSyEO0m?!-fAN@I4u2vjlBe7IL=eT{ z>Q53I=(0^@TitjIpz2`R719Zn9kEVQW4^E z8=xYuJw$|ih2if@_g<$O5sWZ$5t7^2^IDfjbcA1k9&Eha_quDAmoI+^|NY(d9}BwI zT;fJKHm-z>m*10QiGP{zWlI%25Z!Ti$-=06atDB7bcc+V)&f%lPN|=lbXR=lbXR z=lZ{QxdgFrq}6KW)+4zPQd#1#IvgaA7LS?Ab!1w#x6-X-F*}@AuW>w5a$WjF zr;(=9>wo!hV@!-AkybJ~oZ3*cz8&q?A&k1wW+mU`u<7-heR3x9c+3uq-fA%l`{Y_C z(KShiSV{|l8zP6^JD>_K?XiVfjEN41*e``?aT={gy(OJuGdrE;<3`$})oisI7sr@w z`aIf$jhlX3x}L9-Bn?!=~rv^KZ85x@e6Bq3d95*v z1xEDNLOj+x9Y#Jm<=$d8TZ|69mUo9a(Pv_ZS#Ly*&DLa#xRLJtlxM+a)thr|W?BII zOn<-O;|w+PSL8yS8-5uM_jH6}htUyZbtL)}&;x7{oAy&L?iM<&dttC2+e>6H!c?X}s!OVrmb`Ejsu$EYz&XH!T4fWvDmN}T1tpxTAf~dtSBrO>Z zW1`g!SS-cYHOH7^+wr%&+;Ji{;XsRGWWA6zVNTz0HxUx9Xj|k*zkTIhRk7=uGwwv2354J@adwE+%m39olXZEh44XSc`2j zzw>eW@x-##$cRZaT1$wI=>G+vcjQ`K^)Xu#jR-Q?W)#1p<1Hd0qdP~185Ld@<9~6R z(Ha}$j5WLR!INw;)2n$gPFLe4Iu<+9b$SSf@E1&2A}l zB06U#<~|;)&o`Gi#4^dTTF_eLu74er2#z;8sR$wh`a8zV<3_ce?#4-X=;(b)x}Im$ zY}PwnZFxLal);(HKL8#U!GKnQJ-rRQoo6&VZaa|_sDxR}>nNm>BoYyGo{Mo{mDr}Z__7g3IOmpEFzPChmwtBHNJ7i(i5}8W+WYV=31e(=44DnUC2K;X=9no z9Ry&fjyGAn(+L7jv@W(*P=8asC6jQ=yb_8gx5j`QthRIp{zI-3%@(~`_t1n9f!Pzc zwEp76$+Ks^+4HLD_0G>sT>ROj{Mk=TxzqLK)uTU{G(4k8AGt-1)}=es*6m4KH?;PY z6UjSQyf&xk_@hl{KUBZ4wr`xBr#BrOx#rPB`!i2nxPPMr$M;*=b9~Uq z=*v;Nr|({5xfwBVe|`Jmmrl)`eSeM4%bzUT@y6gT@1JV_Mzud;=AG}?uEmv$hrX)5 zb*EwPJmU=gN^{&7-*0>U(a5vIa(j*2HKR(68>9RDd|#Vo`tY<#`@3%MHsf1!)#W!n z?_Jz;@R7AIT=^k;!+&E>*J_?NHskBL>YQ04e!3HoHl`?Oal`tH{EA;b*JpiXhv&`* zztLxOzhA2Ry%t^VuaY4hidLOd*<*EM_cdOY(yH^$?xk!HwhpR3!y=7;OA z>k#BT8po} z5bOVN(c@B&_4n@>_{5(}r#A?!`OTwu{#tcw%bqHYZeRND;QNzKsjsZt@chu`550T4 z#;T>^FQ-4c>95DShAvHOU~k`PV4vM%Kdv@%{NSli#k|>h<+hwJhWD9h%y~=~82aM; zCvT2w=M zw`}Q5zbY-aZtt;k*BkBaS6a61^ZeL%6MCBisy_BX@z8T`jcppVwd)HLrmk2wkh!8)GAln`dw<+IcU|4xNvoBeoovUww--Tvubu5 z*?d9Hm(N_U`sh~UQ|5`A+Yfu*wxRpF(i4RNf2^HQdSA2Iw&%O7`m)xWLw^}jHFIlh z=i0TC^MC$TyzHY(Mb~qOJv{9f^O4QZzw=q`vmL7Dytj1A6;sDvIoC!W8+|49oa62A z>W?k^@RI>iYYW2<{pR=Kl7kE1kC?K0G1c`8(SiNerq4L9)GE-^2CZOn`*9W`1z#Bd4K(z-?!=Mai6`_a7y2V-!{yv?TE{4 zku-Z(UeoVvJ-%E$Vfg#?eqEATv+o2cfRhq2)F(vxqT2(Wm1W?{jEa@A%+@>({?oM zAF=R})b9$mcIuGY_}bGGCR}@Q!sMLppMR7-cyPfx4=lWL-&fsJq|XyhHeY_d$F~Eo zHh*);7p?bn^q=ry&ahVd`yI@xt$REF?PV{w`e;eusOo=oo>k|1&=Ym;^j(s$aBREY z`nX1Mm!@SNedf1m+e3O6J$(_R5$HnKwhrz>Zo~1`6njL_G3Rp4+z}oj2be%LCey9 zt#mC`$DN;C)wb~Twzq2xUaO1Uaety)`@@Gi?tf`n^Q&*%KYHeb`G@UQ4uAi1m3n77 zu;=YZCdwz@p1S#$?o#t@FVs3csO_g$23VGF{^V@;st>do->gM?LBqLCzG+&oSE|+i zYS+LKa}sNGI`+VZ1Hs?q4iA)b2aMl*ZP3#GFGr5=o)f-i@=t9CUOC%Yy?>H&HEY$H zMCHpVxl2Z*X5SwkGjyXqQmx~>m2i7<`m$>=tZKJj-`<=%Wq8eZ&(GhGlfCA~ojOCV z?r&#re#P4S|k z>aH_)n0{7&q-(Dy>z@92-G9g?HP$tWi%Dn_ZmE}Z{aA+$)x)AzS52P3V(qAPV=NZG zv3IuW`j6W-WI}Qezq&u^mNa^G$?$fPS~q{=`xae^F?zjm)rh!XhYk3AmtXIfl21m} zt2tO*aWO`@XqvhH?H|&wb&YR3Qt$uB!M_3%FEl^WckS;d)~)IA=YLO0olY2AtiRav zXRGu0*)yM>8rrgVUSV;c22I<4*zU-o1A8o{IjyJuHErV8FOIR^D%vP@`=R~dHuc+? zE{=)MZG5@S7b_=7NvHjr4U5WNd$rpATW_`+GVmOGL;q;Yj(=@(My_^t%)1f1Ao{nc z*r&(;`QfHDh1EMR>VL3H-{s3r6S^LIVne~s?EV7>j(BRux6?NipDR9cv)^aGUmD-~ z`+?nlF?Ue+KezVB*E)S^s2TR$3#MI@^s_>LS`)u`-iU?0hCOxL_;HU;b!#u1Gc$Sb zjN(Ie$M&6mzjHyA6QAUbTej>IwOOxA{lluPPEWXY=f=snGk@=_d-RoBpPotUT)%_m z#`-a7$JQQ5JNSA3$WNkgE$G-|$*vs>(%QvF99q5ot1*Y|lQ)JOoH_XIfxm8yy;5~V z%&9QTu>7}wX?;0&%5x(&heT#Adogb4fqIkLuYID!wROd%`J0}e)wO8G)YsycMJ!Lb zHRjpSRnw2Di+@sjH|zZD`)39w-JGxA_W8KgId!X*{21`?Q_p_VBKNQ7yRQ9Y4SR$i977UwY+h`COm+e>}Ho;F_4l>%Q%mSv)PNVZ39@cUkTG zKmX9-g_E95T>slLX06Lc{+L~F-~Fc|R|OR9_-TRi+JBMGnP2X#`g7Zx%@ewf>G)9Z zTPxBd#=bOdb&b6vI+V`d^W((AoDIjT)k_(C>8Tq*-~1W1quGbg@9KNl)~U^aO(nNm zef8!;zkKai7~OHkkv0cUJ<)RW_-DR9GWmy(v)eq??2U(REm}08MeBYi<|w~UKUbq~ zi=MT9%zxc*xmD&(-QeH*ycs(CU%h)wuF|l@<*8GTt;#v@IdHzW|DcUH@LU|c`PX$1 zzn;;Booc3b?sL>(SU&nd|4DQ9p3LsQf6S{#r%ZVHm8{2pJiR>0^4qnYVOtvIg-&f0 zw>M!<&1butzc@Qm&F>JizSg;MCG~a(#5ekO+JA=8uY9ucsVDy&aA$Nx(9GO{4-aR5 zOkUT|mbJZQyIP&%7dO?<%Nl<4qdhYd2fjIFd()fmbsc&!uwX&$f=}jN-{0WL#&;U5 z7&Y>Tc9Pz{W!ld#EHt&-ko@h6*4F;rRvl^n$dEg&&L3Q`wB6p%exFrqh&Aw;>L+eA zoPT|4{-#(~I{uL$=b#DBu?w$$ zc`UuozM5)yK%>hKto>%iw7z?DdiL72`fAdq;@pOjuMJwbqF?J}&bh7jEU9sH`y(rM zcQgI;f$o`VC#QA#xE5=Yv#$Q)+v!IybbmWC;_7jytTf&;J3hQ1;>@(K7tSf&S^Qz6 zcVlWad~w#~qk|_78Wm9^bi(-f?{Dkg4E-Zw={~>JSAQDRt90_6xX!O-Eqmuc)6>_g zUORnyZraj0+rIj#YQTeShOSp?^%?O+!4I7_)R;Nq(tG3Qzcb{eL6LO?b{&*0|Kt}$bIs3$geLg z+TZrn;Uh0*wv7C_Fe_tBS5|lK_UP~4`)*;<*3+GPf8L_g;|&hIHYVejc2lbieZS?l zCFg&Sf737S`-9t7f3aigcUK>AynhrndFi<)tG;A8a!r@d>gZO~D(ajyWJbXQcM=P0 zY#%Xj!id(JULP`eQTXM;F%NHfFClQ!TaP@tYf1KlKdxQ5$bVbOfCeorzrFA;c4}S8 zk1?m4#BGb4Fmve3^#()2{A9{6O zyeV~Wi&h;Q2l+)WeXG$!JvVe**QW%E@CRkeUnEA;3Wn&h1 zIdtm&q)j!ZH3{wi%Em!sZGS5tx^hCwGzA>}y7$taS3hi!`q_P7E(l)TI6@!9k1b(- zaD?DLI6_@$I7%TLAv8%B31-lf9EAIU^hpkg^odA`^uwsrbOh^&NRWOM?S|}aNCZqs z80;<9AO{&tWkFT^Bj`gYdIS~(Rv=PGat&z zbd5i^T_H#@fnEn{)bsyPRX;fCh%i_@s1%)H!eM8FhzwzNQBlMVw3xn1281lv2 z3LvIbP#!~JN`T=gYFTnUkYFr#sLIZJ3 zp#`Z410hmCZ9pKDk-H5DIX9OPLJy-jC6Eq8lC|L?6C4o$`ou9p7lI*S^dg}xFo3Gg z!3qwLU@-KN@PCegIN4M(NnwF>rU=5BLbD-ZsFJV^!x-cYA zIQB3ys1ZnVl%5;ZK#)aPFvtRs2n?n!iVXiRcHKO_sj6!mKAFdpIN*S&C=t+NF$KZO zZK2GN1|bZJA^{7E3|2%1L54yQ@TMwNpn%@RsfZw_Qh&h;6sjOZpbCm8Wl)Jw6@jLt z0R)6^t+n?#IZ4BCKi~WQ^ZuTQB{?}~@3q&u)^%O0YKAVtQ2_=5lux|{dI`#_!!p_+ zy(0E3!VbtDHw0Bi2WSEEMf)$nGy=ZX;2`A$w$cNN={@{_&w8->4l@Wk{0(3LSE|9O z0EPlRxPLJ8MmP$f(?OoM8qFee#Nhnw2HqDR9|&GaXS1XC0Yi%B3WUNzyov;j zCfbTV6G;dBI^1qB(d}3X2N)~}k17PV#movYL`PaMwHELV zzbe&I!VI_OHgIn*JJ_T0l^Cl zVb zv%zY>E_VtrmH_P_sENHHTS$)w~?>0YfPZnMt`FQMiv8DDd1j>gUl|ky5rq-ll+LV&nxb6l%?|WFdJDv3N3L~Z z448ErbOv%QdL$BFKzyUyf=s6uA-9|?HZ0G?yg1u=+8iGhP z4j6inNNoljdQ2=0R-*{AO${juI^>Y7S#lN1-fa_8qg%7M-5fxfPpF3o?q64@sj5g0g{kF^Ys>4Olm=4u>03gX^g-` z1|TadT(E%l$kOxqqJOO^ZIAjEpL#Us{Ms!S?t9}v>BbH{S2k|)(#zeqkDj1C{nP4K ztm(~19b9$Y$1mNy=ZP~eVek>%+8MV6+a5bV{l4RQoq~PeKKQ|%Uykas=l2fBOMZU! zp|*C1G}>RZ?$?4h7hadM#=7)mS<}VokM^7S%gl-XH7BfVet({Py}QxCzGCH5^{%n~ zUHJBpGo3#>FXpWHv#frv&!;{pbvjq^^t-it9Lu|XoARc&_oiOY1S_8TV(c@vzN5eK z-IhCe&u@i)r&i_PwWm|1b^o}^ylKCU7i}y_xgM1|MU2! zRy$s)YWiIBVGq84i|M)hmL7VmrRUnv{lBHJea&doKK1Zt7mh6-`Dx#2Q#Q2j^z7G% z2M*ux{K1bND0}v!ofD%^?R$Pqc>TP+yN6v}_GjCn6J~#Rdctwn2b-Fky;pC{aSq+K zKlj`1<9{;UYr3}$z5CqAmv;63?nkY;H1nk+KTdpT{JURGYH&9OJ4O>4cx8x0Vl-5GD}bap|niPLwBUZ;hY zj7=L_r`dSPt~8oWI+N3Ewdsvcv&C#SU!-|$7Jt^22ED^(w+h@xI{`FmSLiS}1zXI5 zah`%$fy1Dw%rgBctS5+iqh4#W#}pt2B%RTzh>;lcD>@@N06}XtIIXtCNc9GTRZE`G zXmdI(5SZd3HD-g8R9T}n+f6o&#%VR_GJ6HaFrGcn8NTTN_9twpC3FCw5xbwsDKP)5C#XDiWo*i9yf zQ5Jd?@mV!0<57qB3}y|HHbNGTp$03$+$JMl7c=@cK@QubH>x0EwV52EXo`36Fn_2R zEm~wE?%-_(U91}vr^Jim#C4qD;On-j&SnvfCM|IXEUFU_CN`&Npx#D#6pW&xU1mNI zXf0GiyV3YxXjigWMHexx1eu@7BFj+V`*$wJF%)$hpcL_>YR1(BU;Q_U=$!> z11Pmd108YclL)anz@ZT|7M;dqfq$k-*4cWy+(KX0OG7;VGVW|a@AsK(CPs^cCEq0bAMvD(mv*k z86;6KgDA5KT|&+cs;0LYuEfU1%{vBQa9w<{jEQA6;StOZ&ZjYX1U`@FNgd}&Q82~O z;fSlDMKtSlCXLRDn&q@QV#I5zD|;Hd&Y=^n+8Dq@Y#bmMhgEAgQiL3LHql|y7?P2& z6~=VC4tdYPeXd305Y&jb8h?aK)3R0*r;H}6U88e?(MM$j*->@3Xu1gBJ7g21!Ot2) zauZ`PTdjhO7}0@;vl$r9Y)sb;mvwO<^+}~mo*y6=mrcJmsl76osj;-|D^Uk+80Q+e zP&thTgI)u9lJ!?_6?Hn{GRf9iAbPDG`se?rTQtjpV{qziX1&R%Wq*QWv@t)%?U+t9 zLljgYD>bGpU=~5o1dPH3p>pW-Ou*_2gw?9ETP$|F&Za~5HwgxlMwK*ea%OXgto#2P z8qgvkv8KNwtEujli?b*sNXOCuz|}i72D@J4Fxs$vom!*GAsC$|lWZ@{g3L4;(dIN- zMJhU!R}%^k3CQl9yYCGPWrmn&g|Ot-7|9rcfZbl=*~~79`5ttSpx{QAo~ z>)-Q7AM4-S(ERD6h8CML+zU>e`M7b>OAWfzTfDdRJ-3u^xw(3yt#GOHI^V*^L+rn8 zXnuNBy^||iotw0DY{OYkq~9^TLA^fDAA0%2t@|^lK3>10_KO|&>_5Ez)Ag03Hb(cf zes6W(9k;h0m4DKA|8IM)eQ9#jA5T89=lx01uB~qDf5V9n_iom%@3FYo=?gu!+;Q6r z`#!IKGNo_JEdzfa_u=8=Q{NuecTxFG(shT{47#@CBi9Ta@7kI+Z~ej@^9O&kq^(W& z@bcSDYn&T|08v1$zu60K+`et>uZAyY?`Z7rH>TU%6R+eya(934o-1am&f2{7s>1v4Z+-2+yDbmQ`a^tqX{5Hx?q&Zm%Mk2nJfT&9=GY+tSa8T1_$w7QX@b<0 zRfANp%h{>044Qwb2{sd&%PbJ3tU?f6R+$ zYfLs;*HkAO5GMqH`UQjy1|c;S28(PYRZu0IEplss6EJtq0&r~rfGi+Om@NnnPOwx? za2$o;A^~5xULn-C2I>aNJ^=c+rVQvACIO-dAi96D)U4Fiz%-B@Q~*dpZkCjbl`Ja* zEr7Wp7b=;Bb3>L~-cEptEDec@)sC`V1`UU)&&>jif+=+(b;FH_Q7{c?kP4lM1niG8 zXh6^}C%^>hwh&mf6QwshwG&u_R51P$1q~i4JDY;W@0>LlbYWR)8JYlKVr0fLyu-P= z4BLMMEH?;r;cqag06k|zJY=I_Tad7lp&q!ZBgBpsNkCS{ZmY=7xW{;Ki{m%gP{YSy@?-d~ytU_$H)R=*gkU zB=qEC3dsl{hp-s}IRtzH^C1P$r&(o%P?lZUS;Pt8lXBTWk%i)00}Yy82;z&~iEg9} zGX*eUX@RW3A>d0GD{9J+FtbpCpvGlcfc#uA_Q19%{ZgmZ#2cWcPP~(>Czz8|6zYFn zDAX|9tOn^LbC?CJ1#MRd*hXlB6ysSyezH(AFm})kAOdD~Fiae53T3HX4UL@(?Os-c zCN~$3kBF!N;m$^Lm032(wKudQ4QlYtaVNFqdvAJrX zl5o1+S=>W_c4uQUR(Hy-01uH0k_3Nom*H0CRB~g5VN@d(CN&O ztuBKDfo1X)wDHyYe6?z!LZ|5S6Qb!iUIz%yQZiMauO_+qTj1E}NGIlO%Z+*j8kT)B`z z;uE+7gF=Q$m;oq5zGTu`8|Ov1(iham8lKLQpneID*YZ|WTQ%l7YJF-Juf<&L;fJb? zpB;Q?axrliRYG5FZ5yKWS~7o3zS_Eo$8ubRwUM9@k&L8xOl+H&26MT9-eBFrO(aB> zyf>)ezW+{Lu{YG;`OslP;J(~i#8X{|=_A*G#qi;SJ|Doc4Vp17cknE;x7>^o#rX<+ zJ}_ffV!JM7kZVVbR|B;q(xK77857=Qz{iFDx0RX|KIRXBArefFPk|@R6l!XhGtP;_=i|((;?uauYav1L zrmNQ1hVxf~Al3RV(@%fc5(RZM(^pW-9hQ&$hfl4Hm&Th+rM}!YwF!9h`P2lZ##rq| zG>^|5idig3Sid0N*uYVIidpns$_)EzNw*Hv`sBF*5)rd;kY6=$YA=BfpThVm&z@vT z0C?9Xvx^0_&`dE&W(VXsWh7mT1!407OIU$VsSI$LI&Y8wj(LAyZA`|%kN7aZzS@FX z-^HQZri~9eTLMoZNkBZ31jHl3iy;NxfI2|RB~%9qr#E>r#d!At_7H z3a~!}i);q`BvG>?KS6IvtnV_YwJ;&5XA*ko4Cq>k5QdeKz{^3ez&`AtR8vU;6_q4J zz9c~#;dP0Sj+Fs*k|aD9%0~v557-+IMK#I*<0L_iFdzxqnH-LUoCKJVP-yTBXs)1+ zQU){^(j0%dwhAE548W(9ks%?!p+SYMQ=J(wuVf%@C1i#QUMdOv1XvCUd=mNzi3z+U zk!0d=r3z?4iJ$@il#q1b+YAI?;RT2RvJ8(&5*h(=xDo;jzybS1lF5={I)L-w0?H6x zmv9H_5|n3bSPA$8g2G$Q)M7JX8u?H7AyvQ;Bs6~k5(tU}k0=QeIc7){_(!-O%3FcvL;_odu1NwO%ODp^r9ydt zV*r1r~Uw)O5g>60ei$1sKmj%u0Z9 z2|YAm2Dt(m3(>@Pk)#2rDiZn&DFXqENx`~h`QQg;5MM_mJPEu3ZI)1*C0^PL+L_)# zU!c{7D;1PI&{YyZ2~a1YBxLXd0O|%vfHQws=Ui#O_NArmOUJYyg934`edOF2G;_a{ z&KNVM6c+>*KR2ed{g~3L#w;3BS~}(&{0l!pLdT4Or%E9O@Ns)T_;e2bEj`x{Zl3{n z!UFJ`(Vm_@S9%VggpIB`*S_>zDO|k`Qmx!#UUu;bIV{;+*2*Vz5!DMEG1z7?kKPPG51Ii(lr1 znGlC_l+INX2AAHPFtKy7!p`AXzlVRrAkaDC)a4N6QEqd>FxG_OGj?!s7|mxGH((qX zCV9`v$tk|1Ck-nD$MSG-YH^r*(i~VeMin{9JZ3TDKQ>9kQdbl?N$kDS(aUqf+-?=;z$6k56MaDb;TX#)%f)plE3wA#P>dYINoEj53X6ZkakB`X zAl}0qh3xXI7pwFpCr9?Y;o{_Wyg1Aht2oRJSI*He%M8?yyl!2h$w8v2Gtq>3EsCiY zIjP|om4?Gg@A322G29S;PBD>KF&!wJM52$@aY~oAG>I0A^+_rkSGl-heC5RX=Y%E< z$E+7*B;MQ?7l&0OPTf@u2xEU%I~4&BCk+>q5mI3R>|_q^TO6jv&`5^EYFHE(lf?|j z{7YCVNf>@$YlR7+$yGJx#lpo2mI)f1F4dJRSgx4K1g0sR;2Tsl5Kb)y1f>>Z$Ano6 zE2oC2QtLKp7=aO1wQ#XYm#IlHk2y*!hLLP@s1;KI+0-66sn~cJLz@%Al|%XYq0H(~C?Edhvjf9(A=F!V zKp9SYgz|epR`T=VR+4}J9{HJtnVFfP5FbwRLnJ`?`OKcHBz}1)GeIyyh558$C{&#v zB9|J%-9!1A=Q7FVBTunEaFw+PejCas@a4nB5O+@@rsCC%hSXLZmxoRAd%#~P1K61% zmMGrdp?ruVAF@)IiED>4GvPCFUJnvUco3T=6r$8&o5F&baz}r|x0(6mRrAj&Rmv}< zD#2Hocs|MAyi=XYP7l2mIwxm5KZKx>>6FR`>*6le@DF@sMqZ}V>grGryiPQVIjF`T z`3RRx5N)zN)wq8s@)Pc-Sn2%g1Q=s|&tc8M&dHDs^&kkqH=K##J+#(x@`#~oOlpWc zBDfr8!uYI#>%e390814_pHC+QhNtgB`KU+vgs~7thIAC73TGBlB9_ziQH?mI`{8JX z@DX27*L=P?9KQz7%yvYYz|G4J`I>#^#Y;GU|J5LT$VkdO>L5{onhImD(EdLVlNVP)pafPy?AnnOI!YF1tb zy!;SPK)HV?3Xw`tsH!LuA{Am?S5OrZgsO;$FQGyxj|gIw7!e}kZ1^m|-(p1Z&1f$o z5nK?yg2y7La$w`fF(p)R;Uu-;c|EfTU6pIVnsx(5MT*-D~Odv z@Pk5u+r^4X5dww_kt)1YiP0<&N+MMexQzG9#gc#J8{)4Jir`sTNsuM867Pf%t1#>Z zurVxISq`y@xGLO=OW}Eik}9~XGJ;XjHH-m*D96tjRs_;mS%S+52 z2hV>But9}bMV}(@63VeeVpXK-Qt~ZEpl<;ikbngdtgHAe#7gfd;-V6f>McZ`6@;P) zwl+ji6)CE$qC5diCPQQum>;2nABRGP=OZliN&x`{NT{MrLXI&)p%QK?$0LgZ2d)Aq z@SLJJISz3mQfH(JtsR_;ny0FYR)#PWCINqW$`L5Ka()Ng!oRL0g+(%lFu0c&DkCg- zdF_~hRaA)OXpPF{kpfaq^jIlYT@S|tSXZezTE!&p)k<6;c(~sZBeC{f5Q+2K5u`-OSVZ976gQO17%9RJRjhx} z(3y&}aS8Z_IkYOVh&+J+fmcHF5nqbzH!5J}1T=G^2!M+-a}jQgFclHm%tb1AZl5hF z5h5j!J&_y?Lp%0bCbg!@!!Vju8MFvlpGXM~EL1a`Vt8w-ECKYnSOTX|cvi#)C}9*) z6d{6QMn~WTRRs=+@D5e2QV<9u3O0Wiu>=!KfNUl3v!LWi6w%WovJ>D4XHpm*@z4CI zfMtx###3Y7DGmfW8E}QzWoe0s%(KDs=oBjvcz{*LGKdKXtej0f5L8US;U@ksRK0{H zHc};&GZcdPBwiw_MYiWwT?S9Qr;8Aa(&_96;aw$%Av_$zATvHdz6isJpq_twfS!mW zfc1fqXws;vfOA)hak&!kGnkPGFCv5@w-3Z7q8BM4oCs*Ngi3)?IIKftf-y#*bOM}^ zs)95MSJ4NC*Y#WicIV?fwMHbF%nT*Vj;pk6Op~Za9xFBiuXs#t0s#HBqGMF zO@!cvz>!5qP^=9d9L0r^T~&XiA_DP75-giQGAqU@F|%w6@1VFK?^HypkiNlxV4ZOM z0A9sN1X-b~1OcfFY!fm$s~f5Vfun*$Ag*p$HnjmQxmv>DVXB3;K2CdBUtV$8N!+_FIm?IavQ*K7JwkU23 zE$2o2@0IKGWk{h;$uEEBXk(-x3h_gMf@eVnIeFJQdQgSzl|WwX4F45*sh>}#31Y&u zB2Wor+s=&`WUE}@ZIFbl;K)c#+{AMaFUD;uaEXUSF9tVJVAcXTQs#Z}qF$wn)L6|w zgf;YzF~6Z8C`Njh!dl(&29QEXtE9dw5#(4AfR%N9a}wJun_hn?c~v<%-dLIxn@6UM zkWzI~QA3ixIB}G~s+G*>3(W9w43)&RRykI=;%{P`iLzCMwW^53FyH^3$%P!2h`cdj zD!C%o=C+8apI8|ohzCWC;@Qe+Jz-`X3IR>rOIFAxh&xSDWm?Lsk{j3vlv|{pW6fDk z?oX_nchn{{R|0>6UQnAW6@D3dI68&OEA~#8c3tG1s?^69g^RJF8gF8Pj`A5SEQ