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/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/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 +) diff --git a/gno.land/pkg/sdk/vm/gas_test.go b/gno.land/pkg/sdk/vm/gas_test.go index 961e401a6e0..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(87965), gasDeliver) + assert.Equal(t, int64(91825), gasDeliver) } // Enough gas for a failed transaction. 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/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/machine.go b/gnovm/pkg/gnolang/machine.go index aa89b643ea6..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) @@ -1738,8 +1801,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 16aeb905173..511b44bfc73 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.GetBase(store) 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. } } 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} 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) } }