From 2c023cf685bf07f7daaeb9a9fd6dce5922a8a403 Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 26 Oct 2023 14:55:14 -0400 Subject: [PATCH 01/20] recurse on DidUpdate for all ArrayValue elements --- gnovm/pkg/gnolang/realm.go | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 567aea58284..ce3d9ca2f08 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -153,14 +153,26 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { rlm.MarkDirty(po) if co != nil { co.IncRefCount() - if co.GetRefCount() > 1 { - if co.GetIsEscaped() { - // already escaped - } else { - rlm.MarkNewEscaped(co) - } + if co.GetRefCount() > 1 && !co.GetIsEscaped() { + rlm.MarkNewEscaped(co) } else if co.GetIsReal() { rlm.MarkDirty(co) + + // A lot of slices are assigned to themselves using the append function. When this assignment happens, + // the underlying array may not need to be reallocated if it has the required capacity. In such a case, + // the array itself would be marked as dirty, but none of the elements that have been appended. This + // is a unique case that requires the solution below -- traverse the array's elements and call `DidUpdate` + // for any of the newly allocated values. We know if they are newly allocated by checking for the + // presence of an object ID. + if value, ok := co.(*ArrayValue); ok { + for _, elem := range value.List { + if obj, ok := elem.V.(Object); ok { + if obj.GetObjectID().IsZero() { + rlm.DidUpdate(co, nil, obj) + } + } + } + } } else { co.SetOwner(po) rlm.MarkNewReal(co) From c4ed7e4be5a0dcfaaddf82a3eda0dd4001471d14 Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 26 Oct 2023 15:29:34 -0400 Subject: [PATCH 02/20] undid incorrect simplification --- gnovm/pkg/gnolang/realm.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index ce3d9ca2f08..00d58a332df 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -153,8 +153,12 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { rlm.MarkDirty(po) if co != nil { co.IncRefCount() - if co.GetRefCount() > 1 && !co.GetIsEscaped() { - rlm.MarkNewEscaped(co) + if co.GetRefCount() > 1 { + if co.GetIsEscaped() { + // already escaped + } else { + rlm.MarkNewEscaped(co) + } } else if co.GetIsReal() { rlm.MarkDirty(co) From 09df263c4b5639ff682e50d110c528baf870eb32 Mon Sep 17 00:00:00 2001 From: deelawn Date: Fri, 27 Oct 2023 09:59:10 -0400 Subject: [PATCH 03/20] reworked solution --- gnovm/pkg/gnolang/realm.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 00d58a332df..5f6a532cbc0 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -151,15 +151,17 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { // Updates to .newCreated/.newEscaped /.newDeleted made here. (first gen) // More appends happen during FinalizeRealmTransactions(). (second+ gen) rlm.MarkDirty(po) + if co != nil { + // If this is a self assignment, then the reference count is decremented again below + // when xo's reference count is decremented. co.IncRefCount() - if co.GetRefCount() > 1 { - if co.GetIsEscaped() { - // already escaped - } else { - rlm.MarkNewEscaped(co) - } - } else if co.GetIsReal() { + if co.GetRefCount() > 1 && !co.GetIsEscaped() { + rlm.MarkNewEscaped(co) + } + + // Marking a value as dirty and increasing its reference count are mutually exclusive operations. + if co.GetIsReal() { rlm.MarkDirty(co) // A lot of slices are assigned to themselves using the append function. When this assignment happens, @@ -182,6 +184,7 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { rlm.MarkNewReal(co) } } + if xo != nil { xo.DecRefCount() if xo.GetRefCount() == 0 { From cec8f0d0b92143c5d53c1107f80d851773b22b47 Mon Sep 17 00:00:00 2001 From: deelawn Date: Fri, 27 Oct 2023 10:40:59 -0400 Subject: [PATCH 04/20] Added txtar test --- gno.land/cmd/gnoland/testdata/append.txtar | 77 ++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 gno.land/cmd/gnoland/testdata/append.txtar diff --git a/gno.land/cmd/gnoland/testdata/append.txtar b/gno.land/cmd/gnoland/testdata/append.txtar new file mode 100644 index 00000000000..6c0cb58d745 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/append.txtar @@ -0,0 +1,77 @@ +# start a new node +gnoland start + +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/append -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 + +# Call Append 1 +gnokey maketx call -pkgpath gno.land/r/append -func Append -gas-fee 1000000ugnot -gas-wanted 2000000 -args '1' -broadcast -chainid=tendermint_test test1 +# Call Append 2 +gnokey maketx call -pkgpath gno.land/r/append -func Append -gas-fee 1000000ugnot -gas-wanted 2000000 -args '2' -broadcast -chainid=tendermint_test test1 +# Call Append 3 +gnokey maketx call -pkgpath gno.land/r/append -func Append -gas-fee 1000000ugnot -gas-wanted 2000000 -args '3' -broadcast -chainid=tendermint_test test1 + +# Call render +gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 + +cmp stdout stdout.golden.1 + +# Call Pop +gnokey maketx call -pkgpath gno.land/r/append -func Pop -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +# Call render +gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 + +cmp stdout stdout.golden.2 + +# Call Append 42 +gnokey maketx call -pkgpath gno.land/r/append -func Append -gas-fee 1000000ugnot -gas-wanted 2000000 -args '42' -broadcast -chainid=tendermint_test test1 + +# Call render +gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 + +cmp stdout stdout.golden.3 + +-- append.gno -- +package append + +import ( + "gno.land/p/demo/ufmt" +) + +type T struct{ i int } + +var a []T + +func init() { + a = make([]T, 0, 1) +} + +func Pop() { + a = append(a[:0], a[1:]...) +} + +func Append(i int) { + a = append(a, T{i: i}) +} + +func Render(path string) string { + var s string + for i:=0;i Date: Tue, 31 Oct 2023 11:34:03 -0700 Subject: [PATCH 05/20] alternate approach to saving nested reference values --- gnovm/pkg/gnolang/realm.go | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 5f6a532cbc0..0b59bf5290f 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -163,22 +163,6 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { // Marking a value as dirty and increasing its reference count are mutually exclusive operations. if co.GetIsReal() { rlm.MarkDirty(co) - - // A lot of slices are assigned to themselves using the append function. When this assignment happens, - // the underlying array may not need to be reallocated if it has the required capacity. In such a case, - // the array itself would be marked as dirty, but none of the elements that have been appended. This - // is a unique case that requires the solution below -- traverse the array's elements and call `DidUpdate` - // for any of the newly allocated values. We know if they are newly allocated by checking for the - // presence of an object ID. - if value, ok := co.(*ArrayValue); ok { - for _, elem := range value.List { - if obj, ok := elem.V.(Object); ok { - if obj.GetObjectID().IsZero() { - rlm.DidUpdate(co, nil, obj) - } - } - } - } } else { co.SetOwner(po) rlm.MarkNewReal(co) @@ -703,10 +687,12 @@ func (rlm *Realm) saveUnsavedObjectRecursively(store Store, oo Object) { for _, uch := range unsaved { if uch.GetIsEscaped() || uch.GetIsNewEscaped() { // no need to save preemptively. - } else { - rlm.saveUnsavedObjectRecursively(store, uch) + continue } + + rlm.saveUnsavedObjectRecursively(store, uch) } + // then, save self. if oo.GetIsNewReal() { // save created object. @@ -715,6 +701,13 @@ func (rlm *Realm) saveUnsavedObjectRecursively(store Store, oo Object) { panic("should not happen") } } + + // This is a nested object that hasn't been created yet. + // Assign of object ID so it can be saved properly. + if oo.GetObjectID().IsZero() { + rlm.assignNewObjectID(oo) + } + rlm.saveObject(store, oo) oo.SetIsNewReal(false) } else { @@ -932,6 +925,12 @@ func getUnsavedChildObjects(val Value) []Object { } else if obj, ok := val.(Object); ok { // if object... if isUnsaved(obj) { + // This is likely a nested composite type that was never "created", so mark it + // at this point so that an ID is assigned and it gets saved correctly. + if obj.GetObjectID().IsZero() { + obj.SetIsNewReal(true) + } + unsaved = append(unsaved, obj) } } else { @@ -1520,7 +1519,7 @@ func refOrCopyValue(parent Object, tv TypedValue) TypedValue { } func isUnsaved(oo Object) bool { - return oo.GetIsNewReal() || oo.GetIsDirty() + return oo.GetIsNewReal() || oo.GetIsDirty() || oo.GetObjectID().IsZero() } func IsRealmPath(pkgPath string) bool { From b2e153a4c6063bff921c75aff316e44da2064009 Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 31 Oct 2023 12:49:03 -0700 Subject: [PATCH 06/20] scoped back which types of values can be saved dynamically --- gnovm/pkg/gnolang/realm.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 0b59bf5290f..b6dc422dca5 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -1519,7 +1519,23 @@ func refOrCopyValue(parent Object, tv TypedValue) TypedValue { } func isUnsaved(oo Object) bool { - return oo.GetIsNewReal() || oo.GetIsDirty() || oo.GetObjectID().IsZero() + return oo.GetIsNewReal() || oo.GetIsDirty() || + (oo.GetObjectID().IsZero() && objCanBeSavedDynamically(oo)) +} + +// objCanBeSavedDynamically returns true if the object's underlying type is one +// that can consist of additional levels of composite typed members; it is not always +// the case that marking parent object as dirty will cause new child objects to be +// created and saved correctly. +func objCanBeSavedDynamically(oo Object) bool { + switch oo.(type) { + case *ArrayValue: + return true + case *StructValue: + return true + } + + return false } func IsRealmPath(pkgPath string) bool { From 4b4b25cb6e1eaea6d1d482e33166de660ca4565f Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 1 Nov 2023 16:17:26 -0700 Subject: [PATCH 07/20] Revert "alternate approach to saving nested reference values" This reverts commit 91b458606e96701284f01708c5b8e8fcea5af8e7. --- gnovm/pkg/gnolang/realm.go | 53 ++++++++++++++------------------------ 1 file changed, 19 insertions(+), 34 deletions(-) diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index b6dc422dca5..5f6a532cbc0 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -163,6 +163,22 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { // Marking a value as dirty and increasing its reference count are mutually exclusive operations. if co.GetIsReal() { rlm.MarkDirty(co) + + // A lot of slices are assigned to themselves using the append function. When this assignment happens, + // the underlying array may not need to be reallocated if it has the required capacity. In such a case, + // the array itself would be marked as dirty, but none of the elements that have been appended. This + // is a unique case that requires the solution below -- traverse the array's elements and call `DidUpdate` + // for any of the newly allocated values. We know if they are newly allocated by checking for the + // presence of an object ID. + if value, ok := co.(*ArrayValue); ok { + for _, elem := range value.List { + if obj, ok := elem.V.(Object); ok { + if obj.GetObjectID().IsZero() { + rlm.DidUpdate(co, nil, obj) + } + } + } + } } else { co.SetOwner(po) rlm.MarkNewReal(co) @@ -687,12 +703,10 @@ func (rlm *Realm) saveUnsavedObjectRecursively(store Store, oo Object) { for _, uch := range unsaved { if uch.GetIsEscaped() || uch.GetIsNewEscaped() { // no need to save preemptively. - continue + } else { + rlm.saveUnsavedObjectRecursively(store, uch) } - - rlm.saveUnsavedObjectRecursively(store, uch) } - // then, save self. if oo.GetIsNewReal() { // save created object. @@ -701,13 +715,6 @@ func (rlm *Realm) saveUnsavedObjectRecursively(store Store, oo Object) { panic("should not happen") } } - - // This is a nested object that hasn't been created yet. - // Assign of object ID so it can be saved properly. - if oo.GetObjectID().IsZero() { - rlm.assignNewObjectID(oo) - } - rlm.saveObject(store, oo) oo.SetIsNewReal(false) } else { @@ -925,12 +932,6 @@ func getUnsavedChildObjects(val Value) []Object { } else if obj, ok := val.(Object); ok { // if object... if isUnsaved(obj) { - // This is likely a nested composite type that was never "created", so mark it - // at this point so that an ID is assigned and it gets saved correctly. - if obj.GetObjectID().IsZero() { - obj.SetIsNewReal(true) - } - unsaved = append(unsaved, obj) } } else { @@ -1519,23 +1520,7 @@ func refOrCopyValue(parent Object, tv TypedValue) TypedValue { } func isUnsaved(oo Object) bool { - return oo.GetIsNewReal() || oo.GetIsDirty() || - (oo.GetObjectID().IsZero() && objCanBeSavedDynamically(oo)) -} - -// objCanBeSavedDynamically returns true if the object's underlying type is one -// that can consist of additional levels of composite typed members; it is not always -// the case that marking parent object as dirty will cause new child objects to be -// created and saved correctly. -func objCanBeSavedDynamically(oo Object) bool { - switch oo.(type) { - case *ArrayValue: - return true - case *StructValue: - return true - } - - return false + return oo.GetIsNewReal() || oo.GetIsDirty() } func IsRealmPath(pkgPath string) bool { From 7df8c8062d5854402e5af0fe4841746c8db963f7 Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 2 Nov 2023 14:51:29 -0700 Subject: [PATCH 08/20] handle nested slices when appending to slices under capacity --- gnovm/pkg/gnolang/realm.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 5f6a532cbc0..6e0027ffa2c 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -176,10 +176,29 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { if obj.GetObjectID().IsZero() { rlm.DidUpdate(co, nil, obj) } + } else if sv, ok := elem.V.(*SliceValue); ok { + if av, ok := sv.Base.(*ArrayValue); ok { + if av.GetObjectID().IsZero() { + // An array has no owner. This is a new instance so increase the reference count and + // mark it as new real so that it and all of its elements get persisted. + av.IncRefCount() + rlm.MarkNewReal(av) + } + } } } } } else { + if co.GetIsNewEscaped() { + panic("should not happen") + } + if co.GetIsEscaped() { + panic("should not happen") + } + if co.GetRefCount() >= 2 { + panic("should not happen") + } + co.SetOwner(po) rlm.MarkNewReal(co) } From 68210832daac76151ca667ccf1e08d14d3111099 Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 7 Nov 2023 13:32:21 -0800 Subject: [PATCH 09/20] wrap new sanity check in debug --- gnovm/pkg/gnolang/realm.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 6e0027ffa2c..836a069cd50 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -189,14 +189,16 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { } } } else { - if co.GetIsNewEscaped() { - panic("should not happen") - } - if co.GetIsEscaped() { - panic("should not happen") - } - if co.GetRefCount() >= 2 { - panic("should not happen") + if debug { + if co.GetIsNewEscaped() { + panic("should not happen") + } + if co.GetIsEscaped() { + panic("should not happen") + } + if co.GetRefCount() >= 2 { + panic("should not happen") + } } co.SetOwner(po) From b4279e9505905171325e0a7a85206bef1dc755f2 Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 8 Nov 2023 08:50:08 -0800 Subject: [PATCH 10/20] fixed up txtar test --- gno.land/cmd/gnoland/testdata/append.txtar | 36 +++++++++------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/gno.land/cmd/gnoland/testdata/append.txtar b/gno.land/cmd/gnoland/testdata/append.txtar index 6c0cb58d745..9008d11e705 100644 --- a/gno.land/cmd/gnoland/testdata/append.txtar +++ b/gno.land/cmd/gnoland/testdata/append.txtar @@ -2,33 +2,42 @@ gnoland start gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/append -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! # Call Append 1 gnokey maketx call -pkgpath gno.land/r/append -func Append -gas-fee 1000000ugnot -gas-wanted 2000000 -args '1' -broadcast -chainid=tendermint_test test1 +stdout OK! + # Call Append 2 gnokey maketx call -pkgpath gno.land/r/append -func Append -gas-fee 1000000ugnot -gas-wanted 2000000 -args '2' -broadcast -chainid=tendermint_test test1 +stdout OK! + # Call Append 3 gnokey maketx call -pkgpath gno.land/r/append -func Append -gas-fee 1000000ugnot -gas-wanted 2000000 -args '3' -broadcast -chainid=tendermint_test test1 +stdout OK! # Call render gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 - -cmp stdout stdout.golden.1 +stdout '("1-2-3-" string)' +stdout OK! # Call Pop gnokey maketx call -pkgpath gno.land/r/append -func Pop -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + # Call render gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 - -cmp stdout stdout.golden.2 +stdout '("2-3-" string)' +stdout OK! # Call Append 42 gnokey maketx call -pkgpath gno.land/r/append -func Append -gas-fee 1000000ugnot -gas-wanted 2000000 -args '42' -broadcast -chainid=tendermint_test test1 +stdout OK! # Call render gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 - -cmp stdout stdout.golden.3 +stdout '("2-3-42-" string)' +stdout OK! -- append.gno -- package append @@ -60,18 +69,3 @@ func Render(path string) string { } return s } --- stdout.golden.1 -- -("1-2-3-" string) -OK! -GAS WANTED: 2000000 -GAS USED: 95179 --- stdout.golden.2 -- -("2-3-" string) -OK! -GAS WANTED: 2000000 -GAS USED: 93546 --- stdout.golden.3 -- -("2-3-42-" string) -OK! -GAS WANTED: 2000000 -GAS USED: 95191 \ No newline at end of file From daa6914242f9136c3d22cf2dfddeb13e4cf4e250 Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 8 Nov 2023 10:18:21 -0800 Subject: [PATCH 11/20] added XX annotation --- gnovm/pkg/gnolang/realm.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 836a069cd50..6e4d5c82f95 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -164,6 +164,8 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { if co.GetIsReal() { rlm.MarkDirty(co) + // XX: there may be a more efficient way to do this; to be revisited. + // // A lot of slices are assigned to themselves using the append function. When this assignment happens, // the underlying array may not need to be reallocated if it has the required capacity. In such a case, // the array itself would be marked as dirty, but none of the elements that have been appended. This From 750ec38adab193f43682e24d09d2bebaf8137904 Mon Sep 17 00:00:00 2001 From: deelawn Date: Mon, 13 Nov 2023 20:22:43 -0800 Subject: [PATCH 12/20] reworked solution to avoid looping in DidUpdate --- gnovm/pkg/gnolang/realm.go | 54 +++++-------------------------------- gnovm/pkg/gnolang/uverse.go | 30 ++++++++++++++------- gnovm/pkg/gnolang/values.go | 22 +++++++++++++++ 3 files changed, 50 insertions(+), 56 deletions(-) diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 6e4d5c82f95..58507f24056 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -153,56 +153,16 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { rlm.MarkDirty(po) if co != nil { - // If this is a self assignment, then the reference count is decremented again below - // when xo's reference count is decremented. co.IncRefCount() - if co.GetRefCount() > 1 && !co.GetIsEscaped() { - rlm.MarkNewEscaped(co) - } - - // Marking a value as dirty and increasing its reference count are mutually exclusive operations. - if co.GetIsReal() { - rlm.MarkDirty(co) - - // XX: there may be a more efficient way to do this; to be revisited. - // - // A lot of slices are assigned to themselves using the append function. When this assignment happens, - // the underlying array may not need to be reallocated if it has the required capacity. In such a case, - // the array itself would be marked as dirty, but none of the elements that have been appended. This - // is a unique case that requires the solution below -- traverse the array's elements and call `DidUpdate` - // for any of the newly allocated values. We know if they are newly allocated by checking for the - // presence of an object ID. - if value, ok := co.(*ArrayValue); ok { - for _, elem := range value.List { - if obj, ok := elem.V.(Object); ok { - if obj.GetObjectID().IsZero() { - rlm.DidUpdate(co, nil, obj) - } - } else if sv, ok := elem.V.(*SliceValue); ok { - if av, ok := sv.Base.(*ArrayValue); ok { - if av.GetObjectID().IsZero() { - // An array has no owner. This is a new instance so increase the reference count and - // mark it as new real so that it and all of its elements get persisted. - av.IncRefCount() - rlm.MarkNewReal(av) - } - } - } - } + if co.GetRefCount() > 1 { + if co.GetIsEscaped() { + // already escaped + } else { + rlm.MarkNewEscaped(co) } + } else if co.GetIsReal() { + rlm.MarkDirty(co) } else { - if debug { - if co.GetIsNewEscaped() { - panic("should not happen") - } - if co.GetIsEscaped() { - panic("should not happen") - } - if co.GetRefCount() >= 2 { - panic("should not happen") - } - } - co.SetOwner(po) rlm.MarkNewReal(co) } diff --git a/gnovm/pkg/gnolang/uverse.go b/gnovm/pkg/gnolang/uverse.go index 57f8f6d393d..306643da8f8 100644 --- a/gnovm/pkg/gnolang/uverse.go +++ b/gnovm/pkg/gnolang/uverse.go @@ -294,14 +294,25 @@ func UverseNode() *PackageNode { // append(*SliceValue.List, *SliceValue) --------- list := xvb.List if argsb.Data == nil { - copy( - list[xvo+xvl:xvo+xvl+argsl], - argsb.List[argso:argso+argsl]) + for i := 0; i < argsl; i++ { + oldElem := list[xvo+xvl+i] + // DeepCopy will resolve references and copy their values to prevent + // reference copying rather than copying the underlying values. + newElem := argsb.List[argso+i].DeepCopy(m.Alloc, m.Store) + list[xvo+xvl+i] = newElem + + m.Realm.DidUpdate( + xvb, + oldElem.GetFirstObject(m.Store), + newElem.GetFirstObject(m.Store), + ) + } } else { copyDataToList( list[xvo+xvl:xvo+xvl+argsl], argsb.Data[argso:argso+argsl], xt.Elem()) + m.Realm.DidUpdate(xvb, nil, nil) } } else { // append(*SliceValue.Data, *SliceValue) --------- @@ -310,6 +321,7 @@ func UverseNode() *PackageNode { copyListToData( data[xvo+xvl:xvo+xvl+argsl], argsb.List[argso:argso+argsl]) + m.Realm.DidUpdate(xvb, nil, nil) } else { copy( data[xvo+xvl:xvo+xvl+argsl], @@ -363,9 +375,9 @@ func UverseNode() *PackageNode { list := make([]TypedValue, xvl+argsl) if 0 < xvl { if xvb.Data == nil { - copy( - list[:xvl], - xvb.List[xvo:xvo+xvl]) + for i := 0; i < xvl; i++ { + list[i] = xvb.List[xvo+i].Copy(m.Alloc) + } } else { panic("should not happen") /* @@ -379,9 +391,9 @@ func UverseNode() *PackageNode { } if 0 < argsl { if argsb.Data == nil { - copy( - list[xvl:xvl+argsl], - argsb.List[argso:argso+argsl]) + for i := 0; i < argsl; i++ { + list[xvl+i] = argsb.List[argso+i].Copy(m.Alloc) + } } else { copyDataToList( list[xvl:xvl+argsl], diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 3bdd3332e08..e819f136d15 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -1010,6 +1010,28 @@ func (tv TypedValue) Copy(alloc *Allocator) (cp TypedValue) { return } +// DeepCopy makes of copy of the underlying value in the case of reference values. +// It copies other values as expected using the normal Copy method. +func (tv TypedValue) DeepCopy(alloc *Allocator, store Store) (cp TypedValue) { + switch tv.V.(type) { + case RefValue: + cp.T = tv.T + refObject := tv.GetFirstObject(store) + switch refObjectValue := refObject.(type) { + case *ArrayValue: + cp.V = refObjectValue.Copy(alloc) + case *StructValue: + cp.V = refObjectValue.Copy(alloc) + default: + cp = tv + } + default: + cp = tv.Copy(alloc) + } + + return +} + // Returns encoded bytes for primitive values. // These bytes are used for both value hashes as well // as hash key bytes. From 7392890d5a81034362f0422da665e74fb2bfcfee Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 14 Nov 2023 09:00:28 -0800 Subject: [PATCH 13/20] txtar test for 1167 test case --- .../cmd/gnoland/testdata/issue-1167.txtar | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 gno.land/cmd/gnoland/testdata/issue-1167.txtar diff --git a/gno.land/cmd/gnoland/testdata/issue-1167.txtar b/gno.land/cmd/gnoland/testdata/issue-1167.txtar new file mode 100644 index 00000000000..a24cca5dce9 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/issue-1167.txtar @@ -0,0 +1,114 @@ +# Reproducible Test for https://github.com/gnolang/gno/issues/1167 + +gnoland start + +# add contract +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/xx -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +# execute New +gnokey maketx call -pkgpath gno.land/r/demo/xx -func New -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +# execute Delta for the first time +gnokey maketx call -pkgpath gno.land/r/demo/xx -func Delta -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! +stdout '"1,1,1;" string' + +# execute Delta for the second time +gnokey maketx call -pkgpath gno.land/r/demo/xx -func Delta -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! +stdout '1,1,1;2,2,2;" string' + +# execute Delta for the third time +gnokey maketx call -pkgpath gno.land/r/demo/xx -func Delta -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! +stdout '1,1,1;2,2,2;3,3,3;" string' + +# execute Render +gnokey maketx call -pkgpath gno.land/r/demo/xx -func Render -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! +stdout '1,1,1;2,2,2;3,3,3;" string' + +-- gno.mod -- +module gno.land/r/demo/xx + +require ( + gno.land/p/demo/avl v0.0.0-latest +) + +-- realm.gno -- +package xx + +import ( + "strconv" + + "gno.land/p/demo/avl" +) + +type Move struct { + N1, N2, N3 byte +} + +type Position struct { + Moves []Move +} + +func (p Position) clone() Position { + mv := p.Moves + return Position{Moves: mv} +} + +func (oldp Position) update() Position { + p := oldp.clone() + + counter++ + // This is a workaround for the wrong behaviour (ie. uncomment this line): + // p.Moves = append([]Move{}, p.Moves...) + p.Moves = append(p.Moves, Move{counter, counter, counter}) + return p +} + +type Game struct { + Position Position +} + +var games avl.Tree // id -> *Game + +var counter byte + +func New(s string) string { + // Bug shows if Moves has a cap > 0 when initialised. + el := &Game{Position: Position{Moves: make([]Move, 0, 2)}} + games.Set(s, el) + return values(el.Position) +} + +func Delta(s string) string { + v, _ := games.Get(s) + g, ok := v.(*Game) + if !ok { + panic("invalid game") + } + n := g.Position.update() + g.Position = n + ret := values(n) + return ret +} + +func Render(s string) string { + v, _ := games.Get(s) + g, ok := v.(*Game) + if !ok { + panic("invalid game") + } + return values(g.Position) +} + +func values(x Position) string { + s := "" + for _, val := range x.Moves { + s += strconv.Itoa(int(val.N1)) + "," + strconv.Itoa(int(val.N2)) + "," + strconv.Itoa(int(val.N3)) + ";" + } + return s +} From 9c0c35a1bdbac25331441f991df1493162273702 Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 14 Nov 2023 16:13:58 -0800 Subject: [PATCH 14/20] improved append test and added more append deep copies --- gno.land/cmd/gnoland/testdata/append.txtar | 44 +++++++++++++++++++++- gnovm/pkg/gnolang/uverse.go | 4 +- gnovm/tests/files/a47.gno | 32 ++++++++++++++++ 3 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 gnovm/tests/files/a47.gno diff --git a/gno.land/cmd/gnoland/testdata/append.txtar b/gno.land/cmd/gnoland/testdata/append.txtar index 9008d11e705..8d3a32e27bd 100644 --- a/gno.land/cmd/gnoland/testdata/append.txtar +++ b/gno.land/cmd/gnoland/testdata/append.txtar @@ -39,6 +39,27 @@ gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot stdout '("2-3-42-" string)' stdout OK! +gnokey maketx call -pkgpath gno.land/r/append -func CopyAppend -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/append -func PopB -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +# Call render +gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +stdout '("2-3-42-" string)' +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/append -func AppendMoreAndC -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/append -func ReassignC -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +stdout '("2-3-42-70-100-" string)' +stdout OK! + -- append.gno -- package append @@ -48,7 +69,9 @@ import ( type T struct{ i int } -var a []T +var a, b []T +var c = []T{{i: 100}} + func init() { a = make([]T, 0, 1) @@ -62,6 +85,25 @@ func Append(i int) { a = append(a, T{i: i}) } +func CopyAppend() { + b = append(a, T{i: 50}, T{i: 60}) +} + +func PopB() { + b = append(b[:0], b[1:]...) +} + +func AppendMoreAndC() { + // Fill to capacity + a = append(a, T{i: 70}) + // Above capacity; make new array + a = append(a, c...) +} + +func ReassignC() { + c[0] = T{i: 200} +} + func Render(path string) string { var s string for i:=0;i Date: Tue, 14 Nov 2023 17:13:24 -0800 Subject: [PATCH 15/20] handle additional append cases --- gno.land/cmd/gnoland/testdata/append.txtar | 22 +++++++++++++++++++--- gnovm/pkg/gnolang/uverse.go | 15 ++++++++------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/gno.land/cmd/gnoland/testdata/append.txtar b/gno.land/cmd/gnoland/testdata/append.txtar index 8d3a32e27bd..792d71882e5 100644 --- a/gno.land/cmd/gnoland/testdata/append.txtar +++ b/gno.land/cmd/gnoland/testdata/append.txtar @@ -8,6 +8,9 @@ stdout OK! gnokey maketx call -pkgpath gno.land/r/append -func Append -gas-fee 1000000ugnot -gas-wanted 2000000 -args '1' -broadcast -chainid=tendermint_test test1 stdout OK! +gnokey maketx call -pkgpath gno.land/r/append -func AppendNil -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + # Call Append 2 gnokey maketx call -pkgpath gno.land/r/append -func Append -gas-fee 1000000ugnot -gas-wanted 2000000 -args '2' -broadcast -chainid=tendermint_test test1 stdout OK! @@ -60,6 +63,10 @@ gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot stdout '("2-3-42-70-100-" string)' stdout OK! +gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args 'd' -broadcast -chainid=tendermint_test test1 +stdout '("1-" string)' +stdout OK! + -- append.gno -- package append @@ -69,7 +76,7 @@ import ( type T struct{ i int } -var a, b []T +var a, b, d []T var c = []T{{i: 100}} @@ -104,10 +111,19 @@ func ReassignC() { c[0] = T{i: 200} } +func AppendNil() { + d = append(d, a...) +} + func Render(path string) string { + source := a + if path == "d" { + source = d + } + var s string - for i:=0;i Date: Wed, 29 Nov 2023 13:04:20 -0800 Subject: [PATCH 16/20] first stab at a solution that fixes the issue --- gnovm/pkg/gnolang/values.go | 48 +++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index e819f136d15..1b49ce8ce3a 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -273,7 +273,35 @@ func (pv PointerValue) Assign2(alloc *Allocator, store Store, rlm *Realm, tv2 Ty // Special case of DataByte into (base=*SliceValue).Data. pv.TV.SetDataByte(tv2.GetUint8()) return + } else if pv.Base == nil && pv.Index == 0 { + // An index of zero indicates this is an assignment to a realm + // object. The base will be nil if this is being assigned to + // a dereferenced pointer value. + + // First resolve the reference value and copy the underlying object + // info along with it. + + // TODO: Should this check that tv2 type is not a reference? + if _, ok := pv.TV.V.(RefValue); ok { + *pv.TV = pv.TV.DeepCopySetRefObjectInfo(alloc, store) + } + + // We need the owner to know which parent object to mark as dirty. + // Otherwise the change will never be persisted. + obj := pv.TV.V.(Object) + pv.Base = obj.GetOwner() + if pv.Base == nil { + // Can't use GetOwnerID() if GetOwner() returns nil. Not sure why this + // returns nil, the the object ID is likely there. Use that to get the object. + objInfo := obj.GetObjectInfo() + if !objInfo.OwnerID.IsZero() { + // Finally use this as the base so DidUpdate is called and all objects + // are correctly persisted to realm storage. + pv.Base = store.GetObject(objInfo.OwnerID) + } + } } + // General case if rlm != nil && pv.Base != nil { oo1 := pv.TV.GetFirstObject(store) @@ -1013,15 +1041,31 @@ func (tv TypedValue) Copy(alloc *Allocator) (cp TypedValue) { // DeepCopy makes of copy of the underlying value in the case of reference values. // It copies other values as expected using the normal Copy method. func (tv TypedValue) DeepCopy(alloc *Allocator, store Store) (cp TypedValue) { + return tv.deepCopy(alloc, store, false) +} + +func (tv TypedValue) DeepCopySetRefObjectInfo(alloc *Allocator, store Store) (cp TypedValue) { + return tv.deepCopy(alloc, store, true) +} + +func (tv TypedValue) deepCopy(alloc *Allocator, store Store, setRefObjectInfo bool) (cp TypedValue) { switch tv.V.(type) { case RefValue: cp.T = tv.T refObject := tv.GetFirstObject(store) switch refObjectValue := refObject.(type) { case *ArrayValue: - cp.V = refObjectValue.Copy(alloc) + arrayValue := refObjectValue.Copy(alloc) + if setRefObjectInfo { + arrayValue.ObjectInfo = *refObject.GetObjectInfo() + } + cp.V = arrayValue case *StructValue: - cp.V = refObjectValue.Copy(alloc) + structValue := refObjectValue.Copy(alloc) + if setRefObjectInfo { + structValue.ObjectInfo = *refObject.GetObjectInfo() + } + cp.V = structValue default: cp = tv } From 9133293f92f18b127c11dbb6cb1315fa73646faa Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 29 Nov 2023 13:18:37 -0800 Subject: [PATCH 17/20] added realm qualifier --- gnovm/pkg/gnolang/values.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 1b49ce8ce3a..9de60e579d3 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -273,7 +273,7 @@ func (pv PointerValue) Assign2(alloc *Allocator, store Store, rlm *Realm, tv2 Ty // Special case of DataByte into (base=*SliceValue).Data. pv.TV.SetDataByte(tv2.GetUint8()) return - } else if pv.Base == nil && pv.Index == 0 { + } else if rlm != nil && pv.Base == nil && pv.Index == 0 { // An index of zero indicates this is an assignment to a realm // object. The base will be nil if this is being assigned to // a dereferenced pointer value. From ed2ad738dff2fb4f9d59e0bd39bdb9e9c2115af4 Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 29 Nov 2023 13:29:17 -0800 Subject: [PATCH 18/20] use object accessor method --- gnovm/pkg/gnolang/values.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 9de60e579d3..bc564c14088 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -288,7 +288,7 @@ func (pv PointerValue) Assign2(alloc *Allocator, store Store, rlm *Realm, tv2 Ty // We need the owner to know which parent object to mark as dirty. // Otherwise the change will never be persisted. - obj := pv.TV.V.(Object) + obj := pv.TV.GetFirstObject(store) pv.Base = obj.GetOwner() if pv.Base == nil { // Can't use GetOwnerID() if GetOwner() returns nil. Not sure why this From e7589fecc00ef0fe441a98fa32250e273f7952d2 Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 7 Dec 2023 13:31:43 -0800 Subject: [PATCH 19/20] add txtar; fix pkg path in other --- .../cmd/gnoland/testdata/issue-1167.txtar | 19 ++---- .../cmd/gnoland/testdata/issue-1326.txtar | 67 +++++++++++++++++++ 2 files changed, 73 insertions(+), 13 deletions(-) create mode 100644 gno.land/cmd/gnoland/testdata/issue-1326.txtar diff --git a/gno.land/cmd/gnoland/testdata/issue-1167.txtar b/gno.land/cmd/gnoland/testdata/issue-1167.txtar index a24cca5dce9..dfab3976a15 100644 --- a/gno.land/cmd/gnoland/testdata/issue-1167.txtar +++ b/gno.land/cmd/gnoland/testdata/issue-1167.txtar @@ -3,40 +3,33 @@ gnoland start # add contract -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/xx -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/xx -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! # execute New -gnokey maketx call -pkgpath gno.land/r/demo/xx -func New -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/xx -func New -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! # execute Delta for the first time -gnokey maketx call -pkgpath gno.land/r/demo/xx -func Delta -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/xx -func Delta -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout '"1,1,1;" string' # execute Delta for the second time -gnokey maketx call -pkgpath gno.land/r/demo/xx -func Delta -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/xx -func Delta -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout '1,1,1;2,2,2;" string' # execute Delta for the third time -gnokey maketx call -pkgpath gno.land/r/demo/xx -func Delta -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/xx -func Delta -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout '1,1,1;2,2,2;3,3,3;" string' # execute Render -gnokey maketx call -pkgpath gno.land/r/demo/xx -func Render -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/xx -func Render -args X -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout '1,1,1;2,2,2;3,3,3;" string' --- gno.mod -- -module gno.land/r/demo/xx - -require ( - gno.land/p/demo/avl v0.0.0-latest -) - -- realm.gno -- package xx diff --git a/gno.land/cmd/gnoland/testdata/issue-1326.txtar b/gno.land/cmd/gnoland/testdata/issue-1326.txtar new file mode 100644 index 00000000000..7da0931f8a3 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/issue-1326.txtar @@ -0,0 +1,67 @@ +# Reproducible Test for https://github.com/gnolang/gno/issues/1167 + +gnoland start + +# add contract +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/xx -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +# execute New +gnokey maketx call -pkgpath gno.land/r/xx -func New -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +# execute Delta for the first time +gnokey maketx call -pkgpath gno.land/r/xx -func Delta -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +-- realm.gno -- +package xx + +import ( + "strconv" + + "gno.land/p/demo/avl" +) + +type Move struct { + N1, N2, N3 byte +} + +type S struct { + Moves []Move +} + +func (s S) clone() S { + mv := s.Moves + return S{Moves: mv} +} + +func (olds S) change() S { + s := olds.clone() + + counter++ + s.Moves = append([]Move{}, s.Moves...) + s.Moves = append(s.Moves, Move{counter, counter, counter}) + return s +} + +var el *S +var counter byte + +func New() { + el = &S{} +} + +func Delta() string { + n := el.change() + *el = n + return Values() +} + +func Values() string { + s := "" + for _, val := range el.Moves { + s += strconv.Itoa(int(val.N1)) + "," + strconv.Itoa(int(val.N2)) + "," + strconv.Itoa(int(val.N3)) + ";" + } + return s +} From d03eec7d2b49bde4023eb567228684abed5f7cbd Mon Sep 17 00:00:00 2001 From: deelawn Date: Mon, 8 Jan 2024 13:53:18 -0800 Subject: [PATCH 20/20] restore object info copy functionality --- gnovm/pkg/gnolang/values.go | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 928022fe9ab..09df5725200 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -284,7 +284,7 @@ func (pv PointerValue) Assign2(alloc *Allocator, store Store, rlm *Realm, tv2 Ty // TODO: Should this check that tv2 type is not a reference? if _, ok := pv.TV.V.(RefValue); ok { - *pv.TV = pv.TV.DeepCopySetRefObjectInfo(alloc, store) + *pv.TV = pv.TV.unrefCopySetRefObjectInfo(alloc, store) } // We need the owner to know which parent object to mark as dirty. @@ -1057,16 +1057,32 @@ func (tv TypedValue) Copy(alloc *Allocator) (cp TypedValue) { // unrefCopy makes a copy of the underlying value in the case of reference values. // It copies other values as expected using the normal Copy method. -func (tv TypedValue) unrefCopy(alloc *Allocator, store Store) (cp TypedValue) { +func (tv TypedValue) unrefCopy(alloc *Allocator, store Store) TypedValue { + return tv.unrefCopyCore(alloc, store, false) +} + +func (tv TypedValue) unrefCopySetRefObjectInfo(alloc *Allocator, store Store) TypedValue { + return tv.unrefCopyCore(alloc, store, true) +} + +func (tv TypedValue) unrefCopyCore(alloc *Allocator, store Store, setRefObjectInfo bool) (cp TypedValue) { switch tv.V.(type) { case RefValue: cp.T = tv.T refObject := tv.GetFirstObject(store) switch refObjectValue := refObject.(type) { case *ArrayValue: - cp.V = refObjectValue.Copy(alloc) + arrayValue := refObjectValue.Copy(alloc) + if setRefObjectInfo { + arrayValue.ObjectInfo = *refObject.GetObjectInfo() + } + cp.V = arrayValue case *StructValue: - cp.V = refObjectValue.Copy(alloc) + structValue := refObjectValue.Copy(alloc) + if setRefObjectInfo { + structValue.ObjectInfo = *refObject.GetObjectInfo() + } + cp.V = structValue default: cp = tv }