Skip to content

Optimize memory representation and operations #707

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

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
2 changes: 2 additions & 0 deletions cli/cli.hs
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,7 @@ vmFromCommand cOpts cExecOpts cFileOpts execOpts= do
, allowFFI = False
, freshAddresses = 0
, beaconRoot = 0
, minMemoryChunk = 1
}
word f def = fromMaybe def (f cExecOpts)
word64 f def = fromMaybe def (f cExecOpts)
Expand Down Expand Up @@ -779,6 +780,7 @@ symvmFromCommand cExecOpts sOpts cFileOpts calldata = do
, allowFFI = False
, freshAddresses = 0
, beaconRoot = 0
, minMemoryChunk = 1
}
word f def = fromMaybe def (f cExecOpts)
word64 f def = fromMaybe def (f cExecOpts)
Expand Down
14 changes: 10 additions & 4 deletions src/EVM.hs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ makeVm o = do
, iterations = mempty
, config = RuntimeConfig
{ allowFFI = o.allowFFI
, minMemoryChunk = o.minMemoryChunk
, baseState = o.baseState
}
, forks = Seq.singleton (ForkState env block cache "")
Expand Down Expand Up @@ -674,9 +675,9 @@ exec1 conf = do
mcopy sz srcOff dstOff = do
m <- gets (.state.memory)
case m of
ConcreteMemory mem -> do
buf <- freezeMemory mem
copyBytesToMemory buf sz srcOff dstOff
ConcreteMemory _ -> do
buf <- readMemory srcOff sz
copyBytesToMemory buf sz (Lit 0) dstOff
SymbolicMemory mem -> do
assign (#state % #memory) (SymbolicMemory $ copySlice srcOff dstOff sz mem mem)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think this previous code results in a bit simpler representation for the symbolic case, so we might want to keep it and only modify the concrete case


Expand Down Expand Up @@ -2953,7 +2954,12 @@ writeMemory memory offset buf = do
expandMemory targetSize = do
let toAlloc = targetSize - VUnboxed.Mutable.length memory
if toAlloc > 0 then do
memory' <- VUnboxed.Mutable.grow memory toAlloc
vm <- get
-- If you are using pure concrete mode, use a large chunk (e.g. 64k).
-- We want to always grow at least a chunk, to avoid the performance impact
-- that would happen with repeated small expansion operations, as grow does
-- a larger *copy* of the vector on a new place
memory' <- VUnboxed.Mutable.grow memory $ max toAlloc vm.config.minMemoryChunk
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

maybe this should be a growth factor rather than a fixed min amount

Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice find! So I had lots of issues in C++ where I was growing by a fixed chunk... (side-note: never use resize, use insert if it will grow later) not sure how relevant that is here, but it was a painful experience :D My suggestion would be to grow some factor, usually 2x is a good rule of thumb, but 1.5x can also work. But perhaps also make sure we grow at least some value (so we don't allocate by small chunks at the beginning). I'd rather avoid repeated allocations. What do you think?

assign (#state % #memory) (ConcreteMemory memory')
pure memory'
else
Expand Down
1 change: 1 addition & 0 deletions src/EVM/Exec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ vmForEthrunCreation creationCode =
, allowFFI = False
, freshAddresses = 0
, beaconRoot = 0
, minMemoryChunk = 1
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need this controllable? We can just set it to a sane default instead? I'd rather not have config options for stuff that a good default works for. It's useful to have this during figuring out what a good default is, but later, it may become a distraction perhaps? What do you think?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

My concern that led me to make this a tunable was symbolic execution. I'm not particularly familiar with how it is represented though so take this with a grain of salt, but what would happen if you end up having a large concrete memory due to the "optimistic growth", which then turns symbolic due to some other reason? How would that get encoded into Z3 et al? Would it hurt performance?

If my concern is unfounded I'm all for picking a good default and not having to have this option 😄

}) <&> set (#env % #contracts % at (LitAddr ethrunAddress))
(Just (initialContract (RuntimeCode (ConcreteRuntimeCode ""))))

Expand Down
2 changes: 2 additions & 0 deletions src/EVM/SymExec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ loadEmptySymVM x callvalue cd =
, allowFFI = False
, freshAddresses = 0
, beaconRoot = 0
, minMemoryChunk = 1
})

-- Creates a symbolic VM that has symbolic storage, unlike loadEmptySymVM
Expand Down Expand Up @@ -285,6 +286,7 @@ loadSymVM x callvalue cd create =
, allowFFI = False
, freshAddresses = 0
, beaconRoot = 0
, minMemoryChunk = 1
})

-- freezes any mutable refs, making it safe to share between threads
Expand Down
2 changes: 2 additions & 0 deletions src/EVM/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,7 @@ data BaseState
-- | Configuration options that need to be consulted at runtime
data RuntimeConfig = RuntimeConfig
{ allowFFI :: Bool
, minMemoryChunk :: Int
, baseState :: BaseState
}
deriving (Show)
Expand Down Expand Up @@ -1021,6 +1022,7 @@ data VMOpts (t :: VMType) = VMOpts
, allowFFI :: Bool
, freshAddresses :: Int
, beaconRoot :: W256
, minMemoryChunk :: Int
}

deriving instance Show (VMOpts Symbolic)
Expand Down
1 change: 1 addition & 0 deletions src/EVM/UnitTest.hs
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ initialUnitTestVm (UnitTestOptions {..}) theContract = do
, allowFFI = ffiAllowed
, freshAddresses = 0
, beaconRoot = 0
, minMemoryChunk = 1
}
let creator =
initialContract (RuntimeCode (ConcreteRuntimeCode ""))
Expand Down
1 change: 1 addition & 0 deletions test/EVM/Test/BlockchainTests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@ fromBlockchainCase' block tx preState postState =
, allowFFI = False
, freshAddresses = 0
, beaconRoot = block.beaconRoot
, minMemoryChunk = 1
})
checkState
postState
Expand Down
1 change: 1 addition & 0 deletions test/EVM/Test/Tracing.hs
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ vmForRuntimeCode runtimecode calldata' evmToolEnv alloc txn fromAddr toAddress =
, allowFFI = False
, freshAddresses = 0
, beaconRoot = 0
, minMemoryChunk = 1
}) <&> set (#env % #contracts % at (LitAddr ethrunAddress))
(Just (initialContract (RuntimeCode (ConcreteRuntimeCode BS.empty))))
<&> set (#state % #calldata) calldata'
Expand Down
1 change: 1 addition & 0 deletions test/rpc.hs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ vmFromRpc blockNum calldata callvalue caller address = do
, allowFFI = False
, freshAddresses = 0
, beaconRoot = 0
, minMemoryChunk = 1
}) <&> set (#cache % #fetched % at address) (Just ctrct)

testRpc :: Text
Expand Down
Loading