packages feed

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

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

{- |
Module      : Stress.LowLevelPipeline
Description : Low-level decoupled execution pipeline (Executor, Store, and ASTModule)
Copyright   : (c) 2026 Tushar Adhatrao
License     : MIT
Stability   : experimental

Exercises the decoupled, low-level execution engine bypassing high-level 'VM':
  * 'createExecutor' / 'deleteExecutor' / 'withExecutor'
  * 'createStore' / 'deleteStore' / 'withStore'
  * 'loadASTFromBytes' / 'loadASTFromFile' / 'deleteASTModule'
  * 'validateAST'
  * 'instantiateModule'
  * 'registerModule'
  * 'registerImportModule'
  * 'registerImportModuleWithAlias'
  * 'invokeFunction' / 'asyncInvokeFunction'
  * High-frequency execution throughput (50,000 invocations)
-}
module Stress.LowLevelPipeline
  ( runLowLevelPipelineSuite
  , LowLevelMetrics (..)
  ) where

import Control.Monad (forM_, unless)
import Data.ByteString qualified as BS
import Data.Int (Int32)
import System.Clock (Clock (Monotonic), diffTimeSpec, getTime, toNanoSecs)
import System.FilePath ((</>))
import System.IO.Temp (withSystemTempDirectory)
import Text.Printf (printf)

import Stress.WasmBytecode (addWasm)
import WasmEdge.Async qualified as WasmAsync
import WasmEdge.Executor
  ( asyncInvokeFunction
  , createExecutor
  , deleteASTModule
  , deleteExecutor
  , instantiateModule
  , invokeFunction
  , loadASTFromBytes
  , loadASTFromFile
  , registerImportModule
  , registerImportModuleWithAlias
  , validateAST
  )
import WasmEdge.Module (buildHostModule, builderAddHostFunc, deleteModuleInstance, findFunction)
import WasmEdge.Store (createStore, deleteStore)
import WasmEdge.Types (WasmVal (..))

data LowLevelMetrics = LowLevelMetrics
  { llmInvocationsCompleted :: !Int
  , llmThroughputOpsPerSec :: !Double
  , llmParityVerified :: !Bool
  , llmFileLoadVerified :: !Bool
  , llmAliasedImportOK :: !Bool
  , llmAsyncInvokeOK :: !Bool
  , llmDurationSec :: !Double
  }
  deriving stock (Show)

runLowLevelPipelineSuite :: IO LowLevelMetrics
runLowLevelPipelineSuite = do
  putStrLn "============================================================"
  putStrLn "  STRESS TEST: Low-Level Decoupled Pipeline (Executor/Store)"
  putStrLn "============================================================"

  t0 <- getTime Monotonic

  -- 1. Lifecycle of Executor and Store
  putStrLn "[1/6] Initializing decoupled Store & Executor contexts..."
  store <- createStore
  executor <- createExecutor Nothing Nothing

  -- 2. Loading ASTModule from bytes and file
  putStrLn "[2/6] Loading ASTModule from bytes and temporary file..."
  resAstFromBytes <- loadASTFromBytes addWasm
  astFromBytes <- case resAstFromBytes of
    Left err -> fail $ "loadASTFromBytes failed: " ++ show err
    Right a -> pure a

  -- Test loadASTFromFile
  fileVerified <- withSystemTempDirectory "wasmedge_lowlevel" $ \tmpDir -> do
    let wasmPath = tmpDir </> "add_temp.wasm"
    BS.writeFile wasmPath addWasm
    resAstFromFile <- loadASTFromFile wasmPath
    case resAstFromFile of
      Left err -> fail $ "loadASTFromFile failed: " ++ show err
      Right astFromFile -> do
        valRes <- validateAST astFromFile
        deleteASTModule astFromFile
        pure (valRes == Right ())

  unless fileVerified $ fail "loadASTFromFile validation failed!"
  putStrLn "      loadASTFromFile verified successfully."

  -- 3. Validating and instantiating ASTModule
  putStrLn "[3/6] Validating and instantiating ASTModule into Store..."
  resVal <- validateAST astFromBytes
  case resVal of
    Left err -> fail $ "validateAST failed: " ++ show err
    Right () -> putStrLn "      validateAST passed."

  resInst <- instantiateModule executor store astFromBytes
  modInst <- case resInst of
    Left err -> fail $ "instantiateModule failed: " ++ show err
    Right m -> pure m

  mAddFn <- findFunction modInst "add"
  addFn <- case mAddFn of
    Nothing -> fail "Function 'add' not found in instantiated module!"
    Just fn -> pure fn

  -- 4. Testing invokeFunction and asyncInvokeFunction
  putStrLn "[4/6] Invoking function synchronously and asynchronously..."
  resSync <- invokeFunction executor addFn [WasmI32 100, WasmI32 200]
  case resSync of
    Right [WasmI32 300] -> putStrLn "      Synchronous invokeFunction(100, 200) -> 300 (VERIFIED)"
    other -> fail $ "Unexpected invokeFunction result: " ++ show other

  asyncHandle <- asyncInvokeFunction executor addFn [WasmI32 500, WasmI32 500]
  resAsync <- WasmAsync.getReturns asyncHandle
  asyncOK <- case resAsync of
    Right [WasmI32 1000] -> do
      putStrLn "      Asynchronous asyncInvokeFunction(500, 500) -> 1000 (VERIFIED)"
      pure True
    other -> fail $ "Unexpected asyncInvokeFunction result: " ++ show other

  -- 5. Testing registerImportModule and registerImportModuleWithAlias
  putStrLn "[5/6] Testing registerImportModule & registerImportModuleWithAlias..."
  hostMod <- buildHostModule "host_math" $ \builder -> do
    builderAddHostFunc builder "host_mul" (\(a :: Int32) (b :: Int32) -> pure (a * b) :: IO Int32)

  resImp1 <- registerImportModule executor store hostMod
  case resImp1 of
    Left err -> fail $ "registerImportModule failed: " ++ show err
    Right () -> putStrLn "      registerImportModule succeeded."

  hostMod2 <- buildHostModule "host_math_alias" $ \builder -> do
    builderAddHostFunc builder "host_sub" (\(a :: Int32) (b :: Int32) -> pure (a - b) :: IO Int32)

  resImp2 <- registerImportModuleWithAlias executor store hostMod2 "custom_math_alias"
  case resImp2 of
    Left err -> fail $ "registerImportModuleWithAlias failed: " ++ show err
    Right () -> putStrLn "      registerImportModuleWithAlias succeeded."

  -- 6. High-Frequency Invocation Benchmark (50,000 iterations)
  putStrLn "[6/6] Benchmarking low-level invocation throughput (50,000 calls)..."
  let totalCalls = 50000 :: Int
  tBench0 <- getTime Monotonic
  forM_ [1 .. totalCalls] $ \i -> do
    res <- invokeFunction executor addFn [WasmI32 (fromIntegral i), WasmI32 1]
    case res of
      Left err -> fail $ "invokeFunction error at iteration " ++ show i ++ ": " ++ show err
      Right _ -> pure ()
  tBench1 <- getTime Monotonic

  let benchNanos = toNanoSecs (diffTimeSpec tBench1 tBench0)
      benchSec = fromIntegral benchNanos / 1e9 :: Double
      opsPerSec = fromIntegral totalCalls / benchSec

  printf
    "      Executed %d calls in %.3f s (%.1f calls/sec)\n"
    totalCalls
    benchSec
    opsPerSec

  deleteASTModule astFromBytes
  deleteModuleInstance modInst
  deleteStore store
  deleteExecutor executor

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

  pure
    LowLevelMetrics
      { llmInvocationsCompleted = totalCalls
      , llmThroughputOpsPerSec = opsPerSec
      , llmParityVerified = True
      , llmFileLoadVerified = fileVerified
      , llmAliasedImportOK = True
      , llmAsyncInvokeOK = asyncOK
      , llmDurationSec = dur
      }