Skip to content

feat(gnovm): fill in 3779, the gc PR #3789

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

Closed
Closed
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
26 changes: 26 additions & 0 deletions gno.land/pkg/integration/testdata/gc.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Init
## deploy realm
loadpkg gno.land/r/gc $WORK/r/gc

## start a new node
gnoland start



gnokey maketx call -pkgpath gno.land/r/gc -func Alloc -gas-fee 100000ugnot -gas-wanted 3000000 -simulate skip -broadcast -chainid tendermint_test test1
stdout 'GAS USED: 576890'


-- r/gc/gc.gno --
package gc

func gen() {
_ = make([]byte, 250*1024*1024)
}

func Alloc() {
for i := 0; i < 100; i++ {
gen()
gen()
}
}
141 changes: 126 additions & 15 deletions gnovm/pkg/gnolang/alloc.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package gnolang

import (
"fmt"
)

// Keeps track of in-memory allocations.
// In the future, allocations within realm boundaries will be
// (optionally?) condensed (objects to be GC'd will be discarded),
// but for now, allocations strictly increment across the whole tx.
type Allocator struct {
maxBytes int64
bytes int64
maxBytes int64
bytes int64
visitCount int64 // times objects are visited for gc
gc func() (int64, bool) // gc callback
}

// for gonative, which doesn't consider the allocator.
Expand All @@ -19,23 +25,27 @@ const (
// gno types
_allocSlice = 24
_allocPointerValue = 40
_allocStructValue = 152
_allocArrayValue = 176
_allocStringValue = 16
_allocStructValue = 160
_allocArrayValue = 184
_allocSliceValue = 40
_allocFuncValue = 136
_allocMapValue = 144
_allocBoundMethodValue = 176
_allocBlock = 464
_allocFuncValue = 196
_allocMapValue = 152
_allocBoundMethodValue = 184
_allocBlock = 480
_allocPackageValue = 248
_allocNativeValue = 48
_allocTypeValue = 16
_allocTypedValue = 40
_allocRefValue = 72
_allocBigint = 200 // XXX
_allocBigdec = 200 // XXX
_allocType = 200 // XXX
_allocAny = 200 // XXX
)

const (
allocString = _allocBase
allocString = _allocBase + _allocPointer + _allocStringValue
allocStringByte = 1
allocBigint = _allocBase + _allocPointer + _allocBigint
allocBigintByte = 1
Expand All @@ -53,12 +63,14 @@ const (
allocBoundMethod = _allocBase + _allocPointer + _allocBoundMethodValue
allocBlock = _allocBase + _allocPointer + _allocBlock
allocBlockItem = _allocTypedValue
allocNative = _allocBase + _allocPointer + _allocNativeValue
allocRefValue = _allocBase + +_allocRefValue
allocType = _allocBase + _allocPointer + _allocType
// allocDataByte = 1
// allocPackge = 1
allocAmino = _allocBase + _allocPointer + _allocAny
allocAminoByte = 10 // XXX
allocHeapItem = _allocBase + _allocPointer + _allocTypedValue
allocDataByte = 1
allocPackage = _allocBase + _allocPointer + _allocPackageValue
allocAmino = _allocBase + _allocPointer + _allocAny
allocAminoByte = 10 // XXX
allocHeapItem = _allocBase + _allocPointer + _allocTypedValue
)

func NewAllocator(maxBytes int64) *Allocator {
Expand All @@ -70,6 +82,18 @@ func NewAllocator(maxBytes int64) *Allocator {
}
}

func (alloc *Allocator) SetGCCallback(f func() (int64, bool)) {
alloc.gc = f
}

func (alloc *Allocator) MemStats() string {
if alloc == nil {
return "nil allocator"
} else {
return fmt.Sprintf("Allocator{maxBytes:%d, bytes:%d}", alloc.maxBytes, alloc.bytes)
}
}

func (alloc *Allocator) Status() (maxBytes int64, bytes int64) {
return alloc.maxBytes, alloc.bytes
}
Expand Down Expand Up @@ -100,7 +124,17 @@ func (alloc *Allocator) Allocate(size int64) {

alloc.bytes += size
if alloc.bytes > alloc.maxBytes {
panic("allocation limit exceeded")
if left, ok := alloc.gc(); !ok {
panic("should not happen, allocation limit exceeded while gc.")
} else { // retry
if debug {
debug.Printf("%d left after GC, required size: %d\n", left, size)
}
alloc.bytes += size
if alloc.bytes > alloc.maxBytes {
panic("allocation limit exceeded")
}
}
}
}

Expand Down Expand Up @@ -299,3 +333,80 @@ func (alloc *Allocator) NewHeapItem(tv TypedValue) *HeapItemValue {
alloc.AllocateHeapItem()
return &HeapItemValue{Value: tv}
}

// -----------------------------------------------

func (pv *PackageValue) GetShallowSize() int64 {
return allocPackage
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these should account for all typedvalues as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is, the TypedValue array size should be included in the block, even if each value itself might have its own allocation on top.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

}

func (b *Block) GetShallowSize() int64 {
return allocBlock
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, see AllocateBlock.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

}

func (av *ArrayValue) GetShallowSize() int64 {
if av.Data != nil {
return allocArray + int64(len(av.Data))
} else {
return allocArray
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here for the else clause.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

}
}

func (sv *StructValue) GetShallowSize() int64 {
return allocStruct
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and so on...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

}

func (mv *MapValue) GetShallowSize() int64 {
return allocMap
}

func (bmv *BoundMethodValue) GetShallowSize() int64 {
return allocBoundMethod
}

func (hiv *HeapItemValue) GetShallowSize() int64 {
return allocHeapItem
}

func (rv RefValue) GetShallowSize() int64 {
fmt.Println("---refValue, get shallow size...")
return allocRefValue
}

func (pv PointerValue) GetShallowSize() int64 {
return allocPointer
}

func (sv *SliceValue) GetShallowSize() int64 {
return allocSlice
}

// Only count for closures.
func (fv *FuncValue) GetShallowSize() int64 {
if fv.IsClosure {
return allocFunc
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems wrong, should be allocFunc no matter what I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the function is defined in preprocess and copied during prepareNewValues, before runtime—so there shouldn’t be any runtime allocation. Am I mistaken?

return 0
}

func (sv StringValue) GetShallowSize() int64 {
return allocString + allocStringByte*int64(len(sv))
}

func (bv BigintValue) GetShallowSize() int64 {
return allocBigint
}

func (bv BigdecValue) GetShallowSize() int64 {
return allocBigdec
}

func (dbv DataByteValue) GetShallowSize() int64 {
return allocDataByte
}

// Do not count during recalculation,
// as the type should pre-exist.
func (tv TypeValue) GetShallowSize() int64 {
return 0
}
Loading
Loading