packages feed

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

{-# LANGUAGE OverloadedStrings #-}

{- |
Module      : Stress.GlobalState
Description : Stress testing WebAssembly Global variables, types, and mutable runtime state
Copyright   : (c) 2026 Tushar Adhatrao
License     : MIT
Stability   : experimental

Exercises:
  * 'createGlobalType' / 'deleteGlobalType' / 'withGlobalType'
  * 'getGlobalTypeValType' / 'getGlobalTypeMutability'
  * 'createGlobal' / 'deleteGlobal' / 'withGlobal'
  * 'getGlobalType' / 'getGlobalValue' / 'setGlobalValue'
  * Mutable vs Immutable globals (const protection fault verification)
  * Multi-type globals (I32, I64, F32, F64)
  * High-frequency mutation stress test (100k updates)
  * Adding globals to 'ModuleBuilder' via 'builderAddGlobal'
-}
module Stress.GlobalState
  ( runGlobalStateSuite
  , GlobalMetrics (..)
  ) where

import Control.Monad (forM_, unless)
import Data.Maybe (isJust)
import System.Clock (Clock (Monotonic), diffTimeSpec, getTime, toNanoSecs)
import Text.Printf (printf)

import WasmEdge.Global
  ( createGlobal
  , createGlobalType
  , deleteGlobal
  , deleteGlobalType
  , disarmGlobal
  , getGlobalTypeMutability
  , getGlobalTypeValType
  , getGlobalValue
  , setGlobalValue
  , withGlobal
  , withGlobalType
  )
import WasmEdge.Module (buildHostModule, builderAddGlobal, findGlobal, listGlobals)
import WasmEdge.Types (Mutability (..), ValType (..), WasmVal (..))

data GlobalMetrics = GlobalMetrics
  { gmUpdatesCompleted :: !Int
  , gmMutationRateOps :: !Double
  , gmImmutableTrapOK :: !Bool
  , gmTypesVerified :: !Int
  , gmHostModuleBound :: !Bool
  , gmDurationSec :: !Double
  }
  deriving stock (Show)

runGlobalStateSuite :: IO GlobalMetrics
runGlobalStateSuite = do
  putStrLn "============================================================"
  putStrLn "  STRESS TEST: Global Instances, Mutability & Runtime State"
  putStrLn "============================================================"

  t0 <- getTime Monotonic

  -- 1. GlobalType creation & metadata queries
  putStrLn "[1/6] Creating GlobalTypes across all primitive types..."
  gtI32Mut <- createGlobalType I32 Var
  gtI64Const <- createGlobalType I64 Const
  gtF32Mut <- createGlobalType F32 Var
  gtF64Mut <- createGlobalType F64 Var

  ty1 <- getGlobalTypeValType gtI32Mut
  mut1 <- getGlobalTypeMutability gtI32Mut
  unless (ty1 == I32 && mut1 == Var) $ fail "gtI32Mut type/mutability mismatch!"

  ty2 <- getGlobalTypeValType gtI64Const
  mut2 <- getGlobalTypeMutability gtI64Const
  unless (ty2 == I64 && mut2 == Const) $ fail "gtI64Const type/mutability mismatch!"

  -- 2. Scoped withGlobalType & withGlobal verification
  putStrLn "[2/6] Verifying scoped withGlobalType & withGlobal..."
  withScopedSuccess <- withGlobalType I32 Var $ \scopedType ->
    withGlobal scopedType (WasmI32 777) $ \scopedGlobal -> do
      val <- getGlobalValue scopedGlobal
      pure (val == WasmI32 777)
  unless withScopedSuccess $ fail "withGlobal scoped lifecycle test failed!"

  -- 3. Multi-type global instance creation
  putStrLn "[3/6] Instantiating multi-type globals (I32, I64, F32, F64)..."
  gI32 <- createGlobal gtI32Mut (WasmI32 42)
  gI64 <- createGlobal gtI64Const (WasmI64 9876543210)
  gF32 <- createGlobal gtF32Mut (WasmF32 3.14159)
  gF64 <- createGlobal gtF64Mut (WasmF64 2.718281828)

  vI32 <- getGlobalValue gI32
  vI64 <- getGlobalValue gI64
  vF32 <- getGlobalValue gF32
  vF64 <- getGlobalValue gF64

  printf
    "      Initial values: I32=%s, I64=%s, F32=%s, F64=%s\n"
    (show vI32)
    (show vI64)
    (show vF32)
    (show vF64)

  -- 4. Immutable mutation rejection (Const protection)
  putStrLn "[4/6] Verifying immutable global mutation rejection..."
  resConstSet <- setGlobalValue gI64 (WasmI64 0)
  immutableTrapOK <- case resConstSet of
    Left _err -> do
      putStrLn "      setGlobalValue on Const global correctly rejected (VERIFIED)"
      pure True
    Right () -> fail "setGlobalValue on Const global should have failed but succeeded!"

  -- 5. High-frequency mutation benchmark (100,000 updates)
  putStrLn "[5/6] Running 100,000 rapid mutation cycles on mutable global..."
  let totalCycles = 100000 :: Int
  tMut0 <- getTime Monotonic
  forM_ [1 .. totalCycles] $ \i -> do
    res <- setGlobalValue gI32 (WasmI32 (fromIntegral i))
    case res of
      Left err -> fail $ "setGlobalValue failed during stress run: " ++ show err
      Right () -> pure ()
  tMut1 <- getTime Monotonic

  vFinal <- getGlobalValue gI32
  unless (vFinal == WasmI32 (fromIntegral totalCycles)) $
    fail "Final value of global does not match expected cycle count!"

  let mutNanos = toNanoSecs (diffTimeSpec tMut1 tMut0)
      mutSec = fromIntegral mutNanos / 1e9 :: Double
      mutOpsSec = fromIntegral totalCycles / mutSec

  printf
    "      Completed %d updates in %.3f s (%.1f updates/sec)\n"
    totalCycles
    mutSec
    mutOpsSec

  -- 6. Integrating Global into Host Module
  putStrLn "[6/6] Integrating GlobalInstance into ModuleBuilder..."
  hostMod <- buildHostModule "custom_runtime_state_mod" $ \builder -> do
    builderAddGlobal builder "runtime_counter" gI32
    builderAddGlobal builder "runtime_pi" gF32
  disarmGlobal gI32
  disarmGlobal gF32

  modGlobals <- listGlobals hostMod
  printf "      Host module globals: %s\n" (show modGlobals)
  unless ("runtime_counter" `elem` modGlobals && "runtime_pi" `elem` modGlobals) $
    fail "Globals missing from host module exports!"

  mFound <- findGlobal hostMod "runtime_counter"
  unless (isJust mFound) $
    fail "findGlobal failed to locate 'runtime_counter'!"

  deleteGlobal gI64
  deleteGlobal gF64
  deleteGlobalType gtI32Mut
  deleteGlobalType gtI64Const
  deleteGlobalType gtF32Mut
  deleteGlobalType gtF64Mut

  t1 <- getTime Monotonic
  let dur = fromIntegral (toNanoSecs (diffTimeSpec t1 t0)) / 1e9 :: Double
  printf ">>> Global State Suite completed in %.3f s\n\n" dur

  pure
    GlobalMetrics
      { gmUpdatesCompleted = totalCycles
      , gmMutationRateOps = mutOpsSec
      , gmImmutableTrapOK = immutableTrapOK
      , gmTypesVerified = 4
      , gmHostModuleBound = True
      , gmDurationSec = dur
      }