Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Bound Method Realm support #1257

Merged
merged 8 commits into from
Jul 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/gno.land/p/demo/tests/p_crossrealm/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/p/demo/tests/p_crossrealm
24 changes: 24 additions & 0 deletions examples/gno.land/p/demo/tests/p_crossrealm/p_crossrealm.gno
Original file line number Diff line number Diff line change
@@ -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())
}
}
29 changes: 29 additions & 0 deletions examples/gno.land/r/demo/tests/crossrealm/crossrealm.gno
Original file line number Diff line number Diff line change
@@ -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,
}
}
6 changes: 6 additions & 0 deletions examples/gno.land/r/demo/tests/crossrealm/gno.mod
Original file line number Diff line number Diff line change
@@ -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
)
2 changes: 1 addition & 1 deletion gno.land/pkg/sdk/vm/gas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion gnovm/cmd/gno/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down
71 changes: 71 additions & 0 deletions gnovm/cmd/gno/testdata/gno_test/realm_boundmethod.txtar
Original file line number Diff line number Diff line change
@@ -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()
}
108 changes: 93 additions & 15 deletions gnovm/pkg/gnolang/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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() {
Expand All @@ -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 {
Expand All @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Expand Down
5 changes: 0 additions & 5 deletions gnovm/pkg/gnolang/ownership.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 8 additions & 1 deletion gnovm/pkg/gnolang/realm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}
}
Expand Down
17 changes: 17 additions & 0 deletions gnovm/tests/files/zrealm_crossrealm14.gno
Original file line number Diff line number Diff line change
@@ -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}
1 change: 1 addition & 0 deletions gnovm/tests/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
Loading