vm: optimize context wrapping code
We can omit DAO wrapping for safe methods and for those methods that are not wrapped into try-catch block. However, we still need to persist notificationsCount changes for these methods to the parent context.
This commit is contained in:
parent
0cd19f550e
commit
ce226f6b76
2 changed files with 24 additions and 7 deletions
|
@ -57,6 +57,9 @@ type Context struct {
|
||||||
// notificationsCount stores number of notifications emitted during current context
|
// notificationsCount stores number of notifications emitted during current context
|
||||||
// handling.
|
// handling.
|
||||||
notificationsCount *int
|
notificationsCount *int
|
||||||
|
// persistNotificationsCountOnUnloading denotes whether notificationsCount should be
|
||||||
|
// persisted to the parent context on current context unloading.
|
||||||
|
persistNotificationsCountOnUnloading bool
|
||||||
// isWrapped tells whether the context's DAO was wrapped into another layer of
|
// isWrapped tells whether the context's DAO was wrapped into another layer of
|
||||||
// MemCachedStore on creation and whether it should be unwrapped on context unloading.
|
// MemCachedStore on creation and whether it should be unwrapped on context unloading.
|
||||||
isWrapped bool
|
isWrapped bool
|
||||||
|
|
28
pkg/vm/vm.go
28
pkg/vm/vm.go
|
@ -357,9 +357,9 @@ func (v *VM) loadScriptWithCallingHash(b []byte, exe *nef.File, caller util.Uint
|
||||||
ctx.scriptHash = hash
|
ctx.scriptHash = hash
|
||||||
ctx.callingScriptHash = caller
|
ctx.callingScriptHash = caller
|
||||||
ctx.NEF = exe
|
ctx.NEF = exe
|
||||||
|
parent := v.Context()
|
||||||
if v.invTree != nil {
|
if v.invTree != nil {
|
||||||
curTree := v.invTree
|
curTree := v.invTree
|
||||||
parent := v.Context()
|
|
||||||
if parent != nil {
|
if parent != nil {
|
||||||
curTree = parent.invTree
|
curTree = parent.invTree
|
||||||
}
|
}
|
||||||
|
@ -368,9 +368,22 @@ func (v *VM) loadScriptWithCallingHash(b []byte, exe *nef.File, caller util.Uint
|
||||||
ctx.invTree = newTree
|
ctx.invTree = newTree
|
||||||
}
|
}
|
||||||
if v.wrapDao != nil {
|
if v.wrapDao != nil {
|
||||||
v.wrapDao()
|
needWrap := f&(callflag.All^callflag.ReadOnly) != 0 // If the method is safe, then it's read-only and doesn't perform storage changes or emit notifications.
|
||||||
ctx.isWrapped = true
|
if !needWrap && parent != nil { // If the method is not wrapped into try-catch block, then changes should be discarded anyway if exception occurs.
|
||||||
|
for i := 0; i < parent.tryStack.Len(); i++ {
|
||||||
|
eCtx := parent.tryStack.Peek(i).Value().(*exceptionHandlingContext)
|
||||||
|
if eCtx.State == eTry {
|
||||||
|
needWrap = true // TODO: is it correct to wrap it only once and break after the first occurrence?
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if needWrap {
|
||||||
|
v.wrapDao()
|
||||||
|
ctx.isWrapped = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
ctx.persistNotificationsCountOnUnloading = true
|
||||||
v.istack.PushItem(ctx)
|
v.istack.PushItem(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1621,7 +1634,7 @@ func (v *VM) unloadContext(ctx *Context) {
|
||||||
if ctx.static != nil && (currCtx == nil || ctx.static != currCtx.static) {
|
if ctx.static != nil && (currCtx == nil || ctx.static != currCtx.static) {
|
||||||
ctx.static.ClearRefs(&v.refs)
|
ctx.static.ClearRefs(&v.refs)
|
||||||
}
|
}
|
||||||
if currCtx == nil || ctx.isWrapped { // In case of CALL, CALLA, CALLL we don't need to commit/discard changes, unwrap DAO and change notificationsCount.
|
if ctx.isWrapped { // In case of CALL, CALLA, CALLL we don't need to commit/discard changes, unwrap DAO and change notificationsCount.
|
||||||
if v.uncaughtException == nil {
|
if v.uncaughtException == nil {
|
||||||
if v.commitChanges != nil {
|
if v.commitChanges != nil {
|
||||||
if err := v.commitChanges(); err != nil {
|
if err := v.commitChanges(); err != nil {
|
||||||
|
@ -1629,15 +1642,15 @@ func (v *VM) unloadContext(ctx *Context) {
|
||||||
panic(fmt.Errorf("failed to commit changes: %w", err))
|
panic(fmt.Errorf("failed to commit changes: %w", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if currCtx != nil {
|
|
||||||
*currCtx.notificationsCount += *ctx.notificationsCount
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if v.revertChanges != nil {
|
if v.revertChanges != nil {
|
||||||
v.revertChanges(*ctx.notificationsCount)
|
v.revertChanges(*ctx.notificationsCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if currCtx != nil && ctx.persistNotificationsCountOnUnloading && !(ctx.isWrapped && v.uncaughtException != nil) {
|
||||||
|
*currCtx.notificationsCount += *ctx.notificationsCount
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getTryParams splits TRY(L) instruction parameter into offsets for catch and finally blocks.
|
// getTryParams splits TRY(L) instruction parameter into offsets for catch and finally blocks.
|
||||||
|
@ -1699,6 +1712,7 @@ func (v *VM) call(ctx *Context, offset int) {
|
||||||
// unloadContext without unnecessary DAO unwrapping and notificationsCount changes.
|
// unloadContext without unnecessary DAO unwrapping and notificationsCount changes.
|
||||||
newCtx.notificationsCount = ctx.notificationsCount
|
newCtx.notificationsCount = ctx.notificationsCount
|
||||||
newCtx.isWrapped = false
|
newCtx.isWrapped = false
|
||||||
|
newCtx.persistNotificationsCountOnUnloading = false
|
||||||
v.istack.PushItem(newCtx)
|
v.istack.PushItem(newCtx)
|
||||||
newCtx.Jump(offset)
|
newCtx.Jump(offset)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue