switch to sparse stack

This commit is contained in:
lukechampine 2020-01-11 20:59:28 -05:00
parent 70299b9d3b
commit 2343930773
1 changed files with 26 additions and 32 deletions

View File

@ -168,8 +168,6 @@ func (cs *chunkState) chunkCounter() uint64 {
return cs.n.counter return cs.n.counter
} }
// complete is a helper method that reports whether a full chunk has been
// processed.
func (cs *chunkState) complete() bool { func (cs *chunkState) complete() bool {
return cs.bytesConsumed == chunkSize return cs.bytesConsumed == chunkSize
} }
@ -246,40 +244,37 @@ func parentNode(left, right [8]uint32, key [8]uint32, flags uint32) node {
type Hasher struct { type Hasher struct {
cs chunkState cs chunkState
key [8]uint32 key [8]uint32
chainStack [54][8]uint32 // space for 54 subtrees (2^54 * chunkSize = 2^64)
stackSize int // number of chainStack elements that are valid
flags uint32 flags uint32
size int // output size, for Sum size int // output size, for Sum
// log(n) set of Merkle subtree roots, at most one per height.
stack [54][8]uint32 // 2^54 * chunkSize = 2^64
used uint64 // bit vector indicating which stack elems are valid; also number of chunks added
}
func (h *Hasher) hasSubtreeAtHeight(i uint64) bool {
return h.used&(1<<i) != 0
} }
// addChunkChainingValue appends a chunk to the right edge of the Merkle tree. // addChunkChainingValue appends a chunk to the right edge of the Merkle tree.
func (h *Hasher) addChunkChainingValue(cv [8]uint32, totalChunks uint64) { func (h *Hasher) addChunkChainingValue(cv [8]uint32) {
// This chunk might complete some subtrees. For each completed subtree, its // seek to first open stack slot, merging subtrees as we go
// left child will be the current top entry in the CV stack, and its right i := uint64(0)
// child will be the current value of cv. Pop each left child off the stack, for ; h.hasSubtreeAtHeight(i); i++ {
// merge it with cv, and overwrite cv with the result. After all these cv = parentNode(h.stack[i], cv, h.key, h.flags).chainingValue()
// merges, push the final value of cv onto the stack. The number of
// completed subtrees is given by the number of trailing 0-bits in the new
// total number of chunks.
for totalChunks&1 == 0 {
// pop and merge
h.stackSize--
cv = parentNode(h.chainStack[h.stackSize], cv, h.key, h.flags).chainingValue()
totalChunks >>= 1
} }
h.chainStack[h.stackSize] = cv h.stack[i] = cv
h.stackSize++ h.used++
} }
// rootNode computes the root of the Merkle tree. It does not modify the // rootNode computes the root of the Merkle tree. It does not modify the
// chainStack. // chainStack.
func (h *Hasher) rootNode() node { func (h *Hasher) rootNode() node {
// Starting with the node from the current chunk, compute all the
// parent chaining values along the right edge of the tree, until we
// have the root node.
n := h.cs.node() n := h.cs.node()
for i := h.stackSize - 1; i >= 0; i-- { for i := uint64(bits.TrailingZeros64(h.used)); i < 64; i++ {
n = parentNode(h.chainStack[i], n.chainingValue(), h.key, h.flags) if h.hasSubtreeAtHeight(i) {
n = parentNode(h.stack[i], n.chainingValue(), h.key, h.flags)
}
} }
n.flags |= flagRoot n.flags |= flagRoot
return n return n
@ -288,7 +283,7 @@ func (h *Hasher) rootNode() node {
// Reset implements hash.Hash. // Reset implements hash.Hash.
func (h *Hasher) Reset() { func (h *Hasher) Reset() {
h.cs = newChunkState(h.key, 0, h.flags) h.cs = newChunkState(h.key, 0, h.flags)
h.stackSize = 0 h.used = 0
} }
// BlockSize implements hash.Hash. // BlockSize implements hash.Hash.
@ -306,9 +301,8 @@ func (h *Hasher) Write(p []byte) (int, error) {
// chunks). // chunks).
if h.cs.complete() { if h.cs.complete() {
cv := h.cs.node().chainingValue() cv := h.cs.node().chainingValue()
totalChunks := h.cs.chunkCounter() + 1 h.addChunkChainingValue(cv)
h.addChunkChainingValue(cv, totalChunks) h.cs = newChunkState(h.key, h.cs.chunkCounter()+1, h.flags)
h.cs = newChunkState(h.key, totalChunks, h.flags)
} }
// Compress input bytes into the current chunk state. // Compress input bytes into the current chunk state.