From b8a8282dbaea40c09288d41dd8ecd734acef3a94 Mon Sep 17 00:00:00 2001 From: jaekwon Date: Fri, 5 Jul 2024 15:34:34 -0700 Subject: [PATCH 1/6] override pv and rlm if method receiver has one --- gnovm/cmd/gno/run_test.go | 2 +- gnovm/pkg/gnolang/machine.go | 17 ++++++++++++++++- gnovm/pkg/gnolang/ownership.go | 5 ----- gnovm/pkg/gnolang/realm.go | 9 ++++++++- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/gnovm/cmd/gno/run_test.go b/gnovm/cmd/gno/run_test.go index 06f9a8dc3a5..79a873cdfe5 100644 --- a/gnovm/cmd/gno/run_test.go +++ b/gnovm/cmd/gno/run_test.go @@ -73,7 +73,7 @@ func TestRunApp(t *testing.T) { }, { args: []string{"run", "-debug-addr", "invalidhost:17538", "../../tests/integ/debugger/sample.gno"}, - errShouldContain: "listen tcp: lookup invalidhost", + errShouldContain: "listen tcp", }, { args: []string{"run", "../../tests/integ/invalid_assign/main.gno"}, diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 864384ea122..22a891da26f 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -1734,8 +1734,23 @@ func (m *Machine) PushFrameCall(cx *CallExpr, fv *FuncValue, recv TypedValue) { if pv == nil { panic(fmt.Sprintf("package value missing in store: %s", fv.PkgPath)) } - m.Package = pv rlm := pv.GetRealm() + if rlm == nil && recv.IsDefined() { + obj := recv.GetFirstObject(m.Store) + if obj == nil { + // could be a nil receiver. + // just ignore. + } else { + recvOID := obj.GetObjectInfo().ID + if !recvOID.IsZero() { + // override the pv and rlm with receiver's. + recvPkgOID := ObjectIDFromPkgID(recvOID.PkgID) + pv = m.Store.GetObject(recvPkgOID).(*PackageValue) + rlm = pv.GetRealm() // done + } + } + } + m.Package = pv if rlm != nil && m.Realm != rlm { m.Realm = rlm // enter new realm } diff --git a/gnovm/pkg/gnolang/ownership.go b/gnovm/pkg/gnolang/ownership.go index 24d70b5dd84..adc096c506b 100644 --- a/gnovm/pkg/gnolang/ownership.go +++ b/gnovm/pkg/gnolang/ownership.go @@ -328,11 +328,6 @@ func (oi *ObjectInfo) GetIsTransient() bool { func (tv *TypedValue) GetFirstObject(store Store) Object { switch cv := tv.V.(type) { case PointerValue: - // TODO: in the future, consider skipping the base if persisted - // ref-count would be 1, e.g. only this pointer refers to - // something in it; in that case, ignore the base. That will - // likely require maybe a preparation step in persistence - // ( or unlikely, a second type of ref-counting). return cv.Base.(Object) case *ArrayValue: return cv diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 3a55b2e14b4..3710524130a 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -73,9 +73,16 @@ func PkgIDFromPkgPath(path string) PkgID { return PkgID{HashBytes([]byte(path))} } +// Returns the ObjectID of the PackageValue associated with path. func ObjectIDFromPkgPath(path string) ObjectID { + pkgID := PkgIDFromPkgPath(path) + return ObjectIDFromPkgID(pkgID) +} + +// Returns the ObjectID of the PackageValue associated with pkgID. +func ObjectIDFromPkgID(pkgID PkgID) ObjectID { return ObjectID{ - PkgID: PkgIDFromPkgPath(path), + PkgID: pkgID, NewTime: 1, // by realm logic. } } From 23f7173e816ae21c10170bb18e9da3300eee5822 Mon Sep 17 00:00:00 2001 From: jaekwon Date: Fri, 5 Jul 2024 15:35:27 -0700 Subject: [PATCH 2/6] add tests --- .../demo/tests/p_crossrealm/p_crossrealm.gno | 24 +++++++ .../r/demo/tests/crossrealm/crossrealm.gno | 29 ++++++++ .../testdata/gno_test/realm_boundmethod.txtar | 71 +++++++++++++++++++ gnovm/pkg/gnolang/values.go | 2 - gnovm/tests/files/zrealm_crossrealm14.gno | 17 +++++ 5 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 examples/gno.land/p/demo/tests/p_crossrealm/p_crossrealm.gno create mode 100644 examples/gno.land/r/demo/tests/crossrealm/crossrealm.gno create mode 100644 gnovm/cmd/gno/testdata/gno_test/realm_boundmethod.txtar create mode 100644 gnovm/tests/files/zrealm_crossrealm14.gno diff --git a/examples/gno.land/p/demo/tests/p_crossrealm/p_crossrealm.gno b/examples/gno.land/p/demo/tests/p_crossrealm/p_crossrealm.gno new file mode 100644 index 00000000000..6d46203e98c --- /dev/null +++ b/examples/gno.land/p/demo/tests/p_crossrealm/p_crossrealm.gno @@ -0,0 +1,24 @@ +package p_crossrealm + +type Stringer interface { + String() string +} + +type Container struct { + A int + B Stringer +} + +func (c *Container) Touch() *Container { + c.A += 1 + return c +} + +func (c *Container) Print() { + println("A:", c.A) + if c.B == nil { + println("B: undefined") + } else { + println("B:", c.B.String()) + } +} diff --git a/examples/gno.land/r/demo/tests/crossrealm/crossrealm.gno b/examples/gno.land/r/demo/tests/crossrealm/crossrealm.gno new file mode 100644 index 00000000000..97273f642de --- /dev/null +++ b/examples/gno.land/r/demo/tests/crossrealm/crossrealm.gno @@ -0,0 +1,29 @@ +package crossrealm + +import ( + "gno.land/p/demo/tests/p_crossrealm" + "gno.land/p/demo/ufmt" +) + +type LocalStruct struct { + A int +} + +func (ls *LocalStruct) String() string { + return ufmt.Sprintf("LocalStruct{%d}", ls.A) +} + +// local is saved locally in this realm +var local *LocalStruct + +func init() { + local = &LocalStruct{A: 123} +} + +// Make1 returns a local object wrapped by a p struct +func Make1() *p_crossrealm.Container { + return &p_crossrealm.Container{ + A: 1, + B: local, + } +} diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_boundmethod.txtar b/gnovm/cmd/gno/testdata/gno_test/realm_boundmethod.txtar new file mode 100644 index 00000000000..9d935df74c2 --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_test/realm_boundmethod.txtar @@ -0,0 +1,71 @@ +# Set up GNOROOT in the current directory. +mkdir $WORK/gnovm +symlink $WORK/gnovm/stdlibs -> $GNOROOT/gnovm/stdlibs +env GNOROOT=$WORK + +gno test -v ./examples/gno.land/r/demo/realm2 + +stderr '=== RUN TestDo' +stderr '--- PASS: TestDo.*' + +stderr '=== RUN file/realm2_filetest.gno' +stderr '--- PASS: file/realm2_filetest.*' + +-- examples/gno.land/p/demo/counter/gno.mod -- +module gno.land/p/demo/counter + +-- examples/gno.land/p/demo/counter/counter.gno -- +package counter + +type Counter struct { + n int +} + +func (c *Counter) Inc() { + c.n++ +} + +-- examples/gno.land/r/demo/realm1/realm1.gno -- +package realm1 + +import "gno.land/p/demo/counter" + +var c = counter.Counter{} + +func GetCounter() *counter.Counter { + return &c +} + +-- examples/gno.land/r/demo/realm2/realm2.gno -- +package realm2 + +import ( + "gno.land/r/demo/realm1" +) + +func Do() { + realm1.GetCounter().Inc() +} + +-- examples/gno.land/r/demo/realm2/realm2_filetest.gno -- +// PKGPATH: gno.land/r/tests +package tests + +import "gno.land/r/demo/realm2" + +func main() { + realm2.Do() + println("OK") +} + +// Output: +// OK + +-- examples/gno.land/r/demo/realm2/realm2_test.gno -- +package realm2 + +import "testing" + +func TestDo(t *testing.T) { + Do() +} diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index d38c083428c..125ae13fbcf 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -192,7 +192,6 @@ const ( PointerIndexNative = -3 // Base is *NativeValue. ) -/* func (pv *PointerValue) GetBase(store Store) Object { switch cbase := pv.Base.(type) { case nil: @@ -207,7 +206,6 @@ func (pv *PointerValue) GetBase(store Store) Object { panic("should not happen") } } -*/ // cu: convert untyped; pass false for const definitions // TODO: document as something that enables into-native assignment. diff --git a/gnovm/tests/files/zrealm_crossrealm14.gno b/gnovm/tests/files/zrealm_crossrealm14.gno new file mode 100644 index 00000000000..23451e6f5d1 --- /dev/null +++ b/gnovm/tests/files/zrealm_crossrealm14.gno @@ -0,0 +1,17 @@ +// PKGPATH: gno.land/r/crossrealm_test +package crossrealm_test + +import ( + crossrealm "gno.land/r/demo/tests/crossrealm" +) + +func main() { + // even though we are running within a realm, + // we aren't storing the result of crossrealm.Make1(), + // so this should print fine. + crossrealm.Make1().Touch().Print() +} + +// Output: +// A: 2 +// B: LocalStruct{123} From b1254a744f23f6697387fe82be2e31c04c430963 Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Fri, 5 Jul 2024 16:11:31 -0700 Subject: [PATCH 3/6] chore: add missing gno.mod files Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/p/demo/tests/p_crossrealm/gno.mod | 1 + examples/gno.land/r/demo/tests/crossrealm/gno.mod | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 examples/gno.land/p/demo/tests/p_crossrealm/gno.mod create mode 100644 examples/gno.land/r/demo/tests/crossrealm/gno.mod diff --git a/examples/gno.land/p/demo/tests/p_crossrealm/gno.mod b/examples/gno.land/p/demo/tests/p_crossrealm/gno.mod new file mode 100644 index 00000000000..8585cfd9c8d --- /dev/null +++ b/examples/gno.land/p/demo/tests/p_crossrealm/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/tests/p_crossrealm diff --git a/examples/gno.land/r/demo/tests/crossrealm/gno.mod b/examples/gno.land/r/demo/tests/crossrealm/gno.mod new file mode 100644 index 00000000000..71a89ec2ec5 --- /dev/null +++ b/examples/gno.land/r/demo/tests/crossrealm/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/demo/tests/crossrealm + +require ( + gno.land/p/demo/tests/p_crossrealm v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) From 6a41a93a8fb22025ddc9197705a69671743fecc1 Mon Sep 17 00:00:00 2001 From: jaekwon Date: Fri, 5 Jul 2024 19:40:46 -0700 Subject: [PATCH 4/6] resave after init --- gnovm/pkg/gnolang/machine.go | 91 ++++++++++++++++++++++++++++++------ gnovm/tests/imports.go | 1 + 2 files changed, 78 insertions(+), 14 deletions(-) diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index d4c152f64a2..850da3d3c0f 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -284,14 +284,29 @@ func (m *Machine) runMemPackage(memPkg *std.MemPackage, save, overrides bool) (* } m.SetActivePackage(pv) // run files. - m.RunFiles(files.Files...) - // maybe save package value and mempackage. + updates := m.RunFileDecls(files.Files...) + // save package value and mempackage. + // XXX save condition will be removed once gonative is removed. + var throwaway *Realm if save { - // store package values and types - m.savePackageValuesAndTypes() + // store new package values and types + throwaway = m.saveNewPackageValuesAndTypes() + if throwaway != nil { + m.Realm = throwaway + } + } + // run init functions + m.runInitFromUpdates(pv, updates) + // save again after init. + if save { + m.resavePackageValues(throwaway) // store mempackage m.Store.AddMemPackage(memPkg) + if throwaway != nil { + m.Realm = nil + } } + return pn, pv } @@ -494,13 +509,27 @@ func (m *Machine) injectLocOnPanic() { } } -// Add files to the package's *FileSet and run them. -// This will also run each init function encountered. +// Convenience for tests. +// Production must not use this, because realm package init +// must happen after persistence and realm finalization, +// then changes from init persisted again. func (m *Machine) RunFiles(fns ...*FileNode) { - m.runFiles(fns...) + pv := m.Package + if pv == nil { + panic("RunFiles requires Machine.Package") + } + updates := m.runFileDecls(fns...) + m.runInitFromUpdates(pv, updates) +} + +// Add files to the package's *FileSet and run decls in them. +// This will also run each init function encountered. +// Returns the updated typed values of package. +func (m *Machine) RunFileDecls(fns ...*FileNode) []TypedValue { + return m.runFileDecls(fns...) } -func (m *Machine) runFiles(fns ...*FileNode) { +func (m *Machine) runFileDecls(fns ...*FileNode) []TypedValue { // Files' package names must match the machine's active one. // if there is one. for _, fn := range fns { @@ -628,11 +657,15 @@ func (m *Machine) runFiles(fns ...*FileNode) { } } - // Run new init functions. - // Go spec: "To ensure reproducible initialization - // behavior, build systems are encouraged to present - // multiple files belonging to the same package in - // lexical file name order to a compiler." + return updates +} + +// Run new init functions. +// Go spec: "To ensure reproducible initialization +// behavior, build systems are encouraged to present +// multiple files belonging to the same package in +// lexical file name order to a compiler." +func (m *Machine) runInitFromUpdates(pv *PackageValue, updates []TypedValue) { for _, tv := range updates { if tv.IsDefined() && tv.T.Kind() == FuncKind && tv.V != nil { fv, ok := tv.V.(*FuncValue) @@ -651,7 +684,10 @@ func (m *Machine) runFiles(fns ...*FileNode) { // Save the machine's package using realm finalization deep crawl. // Also saves declared types. -func (m *Machine) savePackageValuesAndTypes() { +// This happens before any init calls. +// Returns a throwaway realm package is not a realm, +// such as stdlibs or /p/ packages. +func (m *Machine) saveNewPackageValuesAndTypes() (throwaway *Realm) { // save package value and dependencies. pv := m.Package if pv.IsRealm() { @@ -664,6 +700,7 @@ func (m *Machine) savePackageValuesAndTypes() { rlm := NewRealm(pv.PkgPath) rlm.MarkNewReal(pv) rlm.FinalizeRealmTransaction(m.ReadOnly, m.Store) + throwaway = rlm } // save declared types. if bv, ok := pv.Block.(*Block); ok { @@ -675,6 +712,25 @@ func (m *Machine) savePackageValuesAndTypes() { } } } + return +} + +// Resave any changes to realm after init calls. +// Pass in the realm from m.saveNewPackageValuesAndTypes() +// in case a throwaway was created. +func (m *Machine) resavePackageValues(rlm *Realm) { + // save package value and dependencies. + pv := m.Package + if pv.IsRealm() { + rlm = pv.Realm + rlm.FinalizeRealmTransaction(m.ReadOnly, m.Store) + // re-save package realm info. + m.Store.SetPackageRealm(rlm) + } else { // use the throwaway realm. + rlm.FinalizeRealmTransaction(m.ReadOnly, m.Store) + } + // types were already saved, and should not change + // even after running the init function. } func (m *Machine) RunFunc(fn Name) { @@ -815,6 +871,13 @@ func (m *Machine) RunStatement(s Stmt) { // NOTE: to support realm persistence of types, must // first require the validation of blocknode locations. func (m *Machine) RunDeclaration(d Decl) { + if fd, ok := d.(*FuncDecl); ok && fd.Name == "init" { + // XXX or, consider running it, but why would this be needed? + // from a repl there is no need for init() functions. + // Also, there are complications with realms, where + // the realm must be persisted before init(), and persisted again. + panic("Machine.RunDeclaration cannot be used for init functions") + } // Preprocess input using package block. There should only // be one block right now, and it's a *PackageNode. pn := m.LastBlock().GetSource(m.Store).(*PackageNode) diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 86c81be9a18..2fa07fb0fb4 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -89,6 +89,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // pkg := gno.NewPackageNode(gno.Name(memPkg.Name), memPkg.Path, nil) // pv := pkg.NewPackage() // m2.SetActivePackage(pv) + // XXX remove second arg 'false' and remove all gonative stuff. return m2.RunMemPackage(memPkg, false) } } From 7deeca85d3af1c653301e126f878b90ad110c1b6 Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Sat, 6 Jul 2024 07:26:13 -0700 Subject: [PATCH 5/6] chore: fixup Signed-off-by: moul <94029+moul@users.noreply.github.com> --- gno.land/pkg/sdk/vm/gas_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gno.land/pkg/sdk/vm/gas_test.go b/gno.land/pkg/sdk/vm/gas_test.go index 3528f60b0ab..14d6cdb80d7 100644 --- a/gno.land/pkg/sdk/vm/gas_test.go +++ b/gno.land/pkg/sdk/vm/gas_test.go @@ -67,7 +67,7 @@ func TestAddPkgDeliverTx(t *testing.T) { gasDeliver := gctx.GasMeter().GasConsumed() assert.True(t, res.IsOK()) - assert.Equal(t, int64(87929), gasDeliver) + assert.Equal(t, int64(91789), gasDeliver) } // Enough gas for a failed transaction. From a61ab413871dc32344d6c1dbc53dd5225444d460 Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Sat, 6 Jul 2024 07:40:26 -0700 Subject: [PATCH 6/6] chore: fixup Signed-off-by: moul <94029+moul@users.noreply.github.com> --- gno.land/pkg/sdk/vm/gas_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gno.land/pkg/sdk/vm/gas_test.go b/gno.land/pkg/sdk/vm/gas_test.go index 14d6cdb80d7..35706325c20 100644 --- a/gno.land/pkg/sdk/vm/gas_test.go +++ b/gno.land/pkg/sdk/vm/gas_test.go @@ -67,7 +67,7 @@ func TestAddPkgDeliverTx(t *testing.T) { gasDeliver := gctx.GasMeter().GasConsumed() assert.True(t, res.IsOK()) - assert.Equal(t, int64(91789), gasDeliver) + assert.Equal(t, int64(91825), gasDeliver) } // Enough gas for a failed transaction.