Bench.hs (5381B)
1 module Main where 2 3 import Control.Exception (evaluate) 4 import Control.Monad (forM_, unless) 5 import Criterion.Main 6 import Crypto.Hash (hash, Digest, SHA256) 7 import Runtime 8 import TreeObservation (forceTreeShared) 9 import Wire 10 import qualified Data.ByteString as BS 11 import qualified Data.Map as Map 12 import qualified Data.Text as Text 13 import qualified Data.Vector as V 14 import System.Environment (getArgs, withArgs) 15 import System.Exit (die) 16 import System.Timeout (timeout) 17 18 -- Runtime inputs and complete expectations are independent of worker compilation. 19 -- These replace the historical parsing/evaluation timings, not reproduce them. 20 cases :: [(String, String, T, T)] 21 cases = 22 [ ("unchecked head", "uncheckedHead", numbers [1..5], ofNumber 1) 23 , ("head (explicit check)", "checkedHead", numbers [1..5], ok (ofNumber 1)) 24 , ("div (library guards)", "guardedDiv", ofNumber 2, ofNumber 5) 25 , ("div (nonzero boundary)", "checkedDiv", ofNumber 2, ok (ofNumber 5)) 26 , ("div failure (zero boundary)", "checkedDiv", Leaf, err "non-zero") 27 , ("sorted maximum", "sortedMaximum", numbers [1..5], ok (ofNumber 5)) 28 , ("half (even boundary)", "evenHalf", ofNumber 8, ok (ofNumber 4)) 29 , ("sortedList? success", "sortedSum", numbers [1..5], ok (ofNumber 15)) 30 , ("sortedList? failure", "sortedSum", numbers [5,1,3], err "not sorted") 31 , ("listOf nat? success", "naturalSum", numbers [1..5], ok (ofNumber 15)) 32 , ("fn2 apply add", "checkedAdd", ofNumber 3, ok (ofNumber 8)) 33 , ("fnContract apply identity", "checkedIdentity", ofNumber 7, ok (ofNumber 7)) 34 ] 35 where 36 numbers = ofList . map ofNumber 37 ok value = Fork (Stem Leaf) (Fork value Leaf) 38 err message = Fork Leaf (Fork (ofString message) Leaf) 39 40 -- The runtime argument remains outside the timed closure. Observing this Bool 41 -- demands the entire expected canonical result, rather than only its outer tag. 42 {-# NOINLINE observeResult #-} 43 observeResult :: T -> T -> T -> Bool 44 observeResult worker expected input = apply worker input == expected 45 46 loadWorkers :: FilePath -> String -> IO Env 47 loadWorkers path expected = do 48 unless (length expected == 64 && all (`elem` ("0123456789abcdef" :: String)) expected) $ 49 die "expected a lowercase whole-bundle SHA256" 50 bytes <- BS.readFile path 51 unless (show (hash bytes :: Digest SHA256) == expected) $ die "worker bundle SHA256 mismatch" 52 bundle <- either die pure (decodeBundle bytes) 53 either die pure (verifyBundle bundle) 54 let terms = reconstructBundleTerms (bundleNodes bundle) 55 entries = manifestExports (bundleManifest bundle) 56 env = Map.fromList [(Text.unpack (exportName entry), terms V.! fromIntegral (exportRoot entry)) | entry <- entries] 57 wanted = Map.fromList [(name, ()) | (_, name, _, _) <- cases] `Map.union` 58 Map.fromList [(name, ()) | name <- ["uncheckedHash", "inlineHash", "contractHash"]] 59 unless (Map.size env == length entries && Map.keysSet env == Map.keysSet wanted) $ 60 die "worker bundle must have exactly the benchmark export interface" 61 -- Loaded trees are inert/shared. Fully construct all workers before timing. 62 mapM_ forceTreeShared (Map.elems env) 63 pure env 64 65 prepare :: Env -> IO [Benchmark] 66 prepare env = do 67 let worker name = env Map.! name 68 valid = concat (replicate 4 "0123456789abcdef") 69 inputs = 70 [ ("valid", ofString valid, True) 71 , ("invalid-first", ofString ('g' : tail valid), False) 72 , ("invalid-last", ofString (take 63 valid ++ "g"), False) 73 , ("oversized-65", ofString (replicate 65 'a'), False) 74 , ("oversized-65536", ofString (replicate 65536 'a'), False) 75 ] 76 hashWorkers = [("unchecked", "uncheckedHash"), ("inline", "inlineHash"), ("contract", "contractHash")] 77 expectedHash name validInput = if name == "unchecked" || validInput then Stem Leaf else Leaf 78 forM_ cases $ \(label, name, input, expected) -> do 79 forceTreeShared input 80 forceTreeShared expected 81 validCase <- evaluate (observeResult (worker name) expected input) 82 unless validCase $ die ("invalid contract benchmark: " ++ label) 83 forM_ inputs $ \(_, input, expected) -> do 84 forceTreeShared input 85 forM_ hashWorkers $ \(label, name) -> do 86 validCase <- evaluate (observeResult (worker name) (expectedHash label expected) input) 87 unless validCase $ die ("invalid registry boundary benchmark: " ++ label) 88 pure 89 [ bgroup "contracts" 90 [ bench label $ whnf (observeResult (worker name) expected) input 91 | (label, name, input, expected) <- cases ] 92 , bgroup "registry-hash" 93 [ bgroup label 94 [ bench workerLabel $ whnf (observeResult (worker name) (expectedHash workerLabel expected)) input 95 | (workerLabel, name) <- hashWorkers ] 96 | (label, input, expected) <- inputs ] 97 ] 98 99 main :: IO () 100 main = do 101 args <- getArgs 102 case args of 103 path : digest : criterionArgs -> do 104 prepared <- timeout (120 * 1000000) (loadWorkers path digest >>= prepare) 105 benchmarks <- maybe (die "benchmark loading/validation exceeded 120 seconds") pure prepared 106 putStrLn ("Worker bundle SHA256: " ++ digest) 107 if criterionArgs == ["--verify-only"] 108 then putStrLn "All 27 benchmark cases validated; no timings collected." 109 else withArgs criterionArgs (defaultMain benchmarks) 110 _ -> die "usage: tricu-bench WORKERS.arboricx SHA256 [--verify-only | CRITERION_OPTIONS]"