packages feed

wasmedge-hs-0.17.1.0: examples/Stress/BigMemory.hs

{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedStrings #-}

{- |
Module      : Stress.BigMemory
Description : Stress testing large linear memory allocations and data transfers
Copyright   : (c) 2026 Tushar Adhatrao
License     : MIT
Stability   : experimental

Tests multi-megabyte buffer allocations, dynamic memory growth ('memory.grow'),
host-to-guest and guest-to-host data transfers, SHA-256 integrity verification,
and memory isolation between concurrent VMs.
-}
module Stress.BigMemory
  ( runBigMemorySuite
  , BigMemoryMetrics (..)
  ) where

import Control.Concurrent.Async (concurrently)
import Control.Exception (throwIO)
import Control.Monad (unless)
import Crypto.Hash.SHA256 qualified as SHA256
import Data.ByteString qualified as BS
import Data.Text (Text)
import System.Clock (Clock (Monotonic), diffTimeSpec, getTime, toNanoSecs)
import Text.Printf (printf)

import Stress.WasmBytecode (memoryOpsWasm)
import WasmEdge.Memory
  ( createMemory
  , createMemoryType
  , getMemoryData
  , getMemoryPageSize
  , growMemoryPages
  , setMemoryData
  )
import WasmEdge.Module (findMemory)
import WasmEdge.Store (findModule)
import WasmEdge.Types (Limit (..), WasmVal (..))
import WasmEdge.VM

-- | Metrics recorded during the big memory stress test
data BigMemoryMetrics = BigMemoryMetrics
  { bmmInitialPages :: !Int
  , bmmGrownPages :: !Int
  , bmmTransferredMB :: !Double
  , bmmDurationSec :: !Double
  , bmmThroughputMBps :: !Double
  , bmmChecksumValid :: !Bool
  }
  deriving stock (Show)

-- | Run the comprehensive Big Memory stress suite.
runBigMemorySuite :: IO BigMemoryMetrics
runBigMemorySuite = do
  putStrLn "============================================================"
  putStrLn "  STRESS TEST: Big Memory (Multi-MB, Grow, SHA256, Isolation)"
  putStrLn "============================================================"

  start <- getTime Monotonic

  -- 1. Test standalone MemoryInstance creation and dynamic growth
  putStrLn "--> [1/4] Testing standalone MemoryInstance (grow to 32MB / 512 pages)..."
  mt <- createMemoryType (Limit 1 (Just 1024) False)
  mem <- createMemory mt
  initialPages <- getMemoryPageSize mem
  putStrLn $ "    Initial page size: " ++ show initialPages ++ " pages (64 KiB)"

  -- Grow by 511 pages (to 512 pages = 32MB)
  growRes <- growMemoryPages mem 511
  case growRes of
    Left err -> throwIO (userError $ "Failed to grow memory: " ++ show err)
    Right () -> pure ()
  newPages <- getMemoryPageSize mem
  putStrLn $ "    New page size: " ++ show newPages ++ " pages (32 MiB)"

  -- 2. Test writing 8MB from Haskell into MemoryInstance and reading it back with SHA-256
  putStrLn "--> [2/4] Streaming 8MB from host into memory, validating SHA-256..."
  let eightMB = 8 * 1024 * 1024
      chunk = BS.replicate eightMB 0x7E
      expectedHash = SHA256.hash chunk

  writeRes <- setMemoryData mem 0 chunk
  case writeRes of
    Left err -> throwIO (userError $ "Failed to write memory: " ++ show err)
    Right () -> pure ()

  readRes <- getMemoryData mem 0 (fromIntegral eightMB)
  readBytes <- case readRes of
    Left err -> throwIO (userError $ "Failed to read memory: " ++ show err)
    Right bs -> pure bs

  let actualHash = SHA256.hash readBytes
      hashMatches = expectedHash == actualHash
  putStrLn $ "    SHA-256 verification: " ++ if hashMatches then "PASSED" else "FAILED"
  unless hashMatches $
    throwIO (userError "SHA-256 hash mismatch on 8MB buffer transfer!")

  -- 3. Test running WASM guest with dynamic memory.grow and memory inspection via VM
  putStrLn "--> [3/4] Registering memoryOpsWasm in VM, growing memory from inside guest..."
  (grownPagesCount, guestHashOk) <- withVM Nothing Nothing $ \vm -> do
    -- Register as named module so we can query it from Store
    let modName = "mem_test" :: Text
    regRes <- registerModuleFromBytes vm modName memoryOpsWasm
    case regRes of
      Left err -> throwIO (userError $ "Failed to register module: " ++ show err)
      Right () -> pure ()

    -- Call memory_size (should be 1 page)
    Right [WasmI32 initPages] <- executeRegistered vm modName "memory_size" []
    putStrLn $ "    Guest reports memory_size = " ++ show initPages ++ " pages"

    -- Call grow_memory 255 (to 256 pages = 16MB)
    Right [WasmI32 prevPages] <- executeRegistered vm modName "grow_memory" [WasmI32 255]
    putStrLn $ "    Guest memory.grow returned previous size = " ++ show prevPages

    -- Call memory_size (now 256)
    Right [WasmI32 currentPages] <- executeRegistered vm modName "memory_size" []
    putStrLn $ "    Guest reports new memory_size = " ++ show currentPages ++ " pages (16 MiB)"

    -- Execute fill_pattern in guest: offset=0, len=1,048,576 (1MB), val=0x42 ('B')
    let oneMB = 1024 * 1024
    Right [WasmI32 guestSum] <-
      executeRegistered vm modName "fill_pattern" [WasmI32 0, WasmI32 (fromIntegral oneMB), WasmI32 0x42]
    let expectedSum = oneMB * 0x42 :: Int
    putStrLn $
      "    Guest fill_pattern sum: " ++ show guestSum ++ " (expected " ++ show expectedSum ++ ")"

    -- Retrieve MemoryInstance from VM Store and verify bytes directly from host
    store <- getStore vm
    mMod <- findModule store modName
    case mMod of
      Nothing -> throwIO (userError "Module not found in store!")
      Just mi -> do
        mGuestMem <- findMemory mi "memory"
        case mGuestMem of
          Nothing -> throwIO (userError "Exported 'memory' not found on module instance!")
          Just guestMem -> do
            Right hostReadBS <- getMemoryData guestMem 0 (fromIntegral oneMB)
            let hostHash = SHA256.hash hostReadBS
                expectedHostBS = BS.replicate oneMB 0x42
                guestExpectedHash = SHA256.hash expectedHostBS
            pure (fromIntegral currentPages, hostHash == guestExpectedHash)

  -- 4. Test Memory Isolation between concurrent VMs
  putStrLn "--> [4/4] Testing memory isolation between 2 concurrent VMs..."
  let twoMB = 2 * 1024 * 1024
      patternA = BS.replicate twoMB 0xAA
      patternB = BS.replicate twoMB 0xBB

  (isoA, isoB) <-
    concurrently
      ( withVM Nothing Nothing $ \vmA -> do
          _ <- registerModuleFromBytes vmA "modA" memoryOpsWasm
          _ <- executeRegistered vmA "modA" "grow_memory" [WasmI32 63] -- 64 pages = 4MB
          storeA <- getStore vmA
          Just miA <- findModule storeA "modA"
          Just memA <- findMemory miA "memory"
          _ <- setMemoryData memA 0 patternA
          Right readA <- getMemoryData memA 0 (fromIntegral twoMB)
          pure (readA == patternA)
      )
      ( withVM Nothing Nothing $ \vmB -> do
          _ <- registerModuleFromBytes vmB "modB" memoryOpsWasm
          _ <- executeRegistered vmB "modB" "grow_memory" [WasmI32 63] -- 64 pages = 4MB
          storeB <- getStore vmB
          Just miB <- findModule storeB "modB"
          Just memB <- findMemory miB "memory"
          _ <- setMemoryData memB 0 patternB
          Right readB <- getMemoryData memB 0 (fromIntegral twoMB)
          pure (readB == patternB)
      )

  if isoA && isoB
    then putStrLn "    Memory isolation test: PASSED (zero cross-talk)"
    else throwIO (userError "Memory isolation test FAILED!")

  end <- getTime Monotonic
  let diffNanos = toNanoSecs (diffTimeSpec end start)
      durSec = fromIntegral diffNanos / 1e9 :: Double
      totalTransferredMB = 8.0 + 1.0 + 4.0 -- 8MB + 1MB + 4MB
      throughput = totalTransferredMB / durSec

  printf "==> Big Memory Test PASSED in %.3f s (%.2f MB/s throughput)\n\n" durSec throughput
  pure $
    BigMemoryMetrics
      { bmmInitialPages = fromIntegral initialPages
      , bmmGrownPages = grownPagesCount
      , bmmTransferredMB = totalTransferredMB
      , bmmDurationSec = durSec
      , bmmThroughputMBps = throughput
      , bmmChecksumValid = hashMatches && guestHashOk && isoA && isoB
      }