From 3f15d100bedf04fc55ffc254b577a8c4c630d443 Mon Sep 17 00:00:00 2001 From: Eric Myhre Date: Mon, 9 Aug 2021 11:43:37 +0200 Subject: [PATCH] wip: prototyping what hamt amending might look like. --- basic_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ builder.go | 30 +++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/basic_test.go b/basic_test.go index 6bf4500..20fccf5 100644 --- a/basic_test.go +++ b/basic_test.go @@ -319,3 +319,46 @@ func TestBuilder(t *testing.T) { qt.Assert(t, node1.Length(), qt.Equals, int64(1)) qt.Assert(t, node2.Length(), qt.Equals, int64(2)) } + +func TestCOW(t *testing.T) { + t.Parallel() + + builder := Prototype{}.NewBuilder() + assembler, err := builder.BeginMap(0) + qt.Assert(t, err, qt.IsNil) + + qt.Assert(t, assembler.AssembleKey().AssignString("foo"), qt.IsNil) + qt.Assert(t, assembler.AssembleValue().AssignString("bar1"), qt.IsNil) + qt.Assert(t, assembler.Finish(), qt.IsNil) + + node := builder.Build() + + qt.Assert(t, node.Length(), qt.Equals, int64(1)) + + val, err := node.LookupByString("foo") + qt.Assert(t, err, qt.IsNil) + valStr, err := val.AsString() + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, valStr, qt.Equals, "bar1") + + builder.Reset() + builder.(*Builder).StartWith(node) + qt.Assert(t, assembler.AssembleKey().AssignString("foo2"), qt.IsNil) + qt.Assert(t, assembler.AssembleValue().AssignString("bar2"), qt.IsNil) + qt.Assert(t, assembler.Finish(), qt.IsNil) + + node2 := builder.Build() + + qt.Check(t, node2.Length(), qt.Equals, int64(2)) + + val, err = node2.LookupByString("foo") + qt.Assert(t, err, qt.IsNil) + valStr, err = val.AsString() + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, valStr, qt.Equals, "bar1") + val, err = node2.LookupByString("foo2") + qt.Assert(t, err, qt.IsNil) + valStr, err = val.AsString() + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, valStr, qt.Equals, "bar2") +} diff --git a/builder.go b/builder.go index aaac3f1..fbf1a64 100644 --- a/builder.go +++ b/builder.go @@ -96,7 +96,7 @@ func (b *Builder) BeginMap(sizeHint int64) (ipld.MapAssembler, error) { return &assembler{node: b.node}, nil } if b.bitWidth < 3 { - return nil, fmt.Errorf("bitWidth must bee at least 3") + return nil, fmt.Errorf("bitWidth must be at least 3") } switch b.hashAlg { case multicodec.Identity, multicodec.Sha2_256, multicodec.Murmur3_128: @@ -112,6 +112,34 @@ func (b *Builder) BeginMap(sizeHint int64) (ipld.MapAssembler, error) { } return &assembler{node: b.node}, nil } + +// StartWith is an extension to a NodeBuilder which advertises that it can make +// new data structures with copy-on-write internal memory. +// +// It's particularly impactful for something like this HAMT ADL, +// since it means an appended data structure can be made without lots of copying, +// and also no need to re-compute the sharding, bucketing, etc for all that data. +func (b *Builder) StartWith(n ipld.Node) (ipld.MapAssembler, error) { + // Of course, we can only really do this well if the node you give us is the type we know well. + if n2, familiar := n.(*Node); familiar { + if b.bitWidth != n2.bitWidth() { + panic(fmt.Errorf("todo: fallback to a copy? distinct bitwidth; %v %v", b.bitWidth, n2.bitWidth())) + } + //if b.hashAlg != n2.hashAlg() { // FIXME oh dear. This is not recalled and we cannot check it. (It would be an impossible claim to verify for something serial, anyway, I suppose.) That... provokes interesting conversations. + // panic("todo: fallback to a copy?") + //} + if b.bucketSize != n2._bucketSize() { + panic(fmt.Errorf("todo: fallback to a copy? distinct bucket size; %v %v", b.bucketSize, n2._bucketSize())) + } + // It's also particularly easy to implement this confidently since the internal data structures are immutable in memory, too. + // TODO ^ check if this claim is actually true. I am not sure it is. I think I see touching of the innards. + // We already have a new Node allocated (we assume) by either NewBuilder or by Reset; we'll just smash the memory of the node we're starting with on top of that, and then we go from there.3 + *b.node = *n2 + return &assembler{node: b.node}, nil + } + panic("todo: fallback to a copy? not this adl impl at all") // REVIEW what the contract of this should be. I dislike things that can be "suprise! slow because of argument misalignment we didn't raise to you". + // I suppose if we have a well-typed error for this, we can make the decision about whether to fall back to a dumb copy or not something that the top level IPLD functions can choose (...assuming that we also pursue that thought). +} func (b *Builder) BeginList(sizeHint int64) (ipld.ListAssembler, error) { panic("todo: error?") } func (b *Builder) AssignNull() error { panic("todo: error?") } func (b *Builder) AssignBool(bool) error { panic("todo: error?") }