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:
Anna Shaleva 2022-05-23 08:42:10 +03:00
parent 0cd19f550e
commit ce226f6b76
2 changed files with 24 additions and 7 deletions

View file

@ -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

View file

@ -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 {
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.
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() v.wrapDao()
ctx.isWrapped = true 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)
} }