packages feed

tala-core-0.1.0: Benches/Main.hs

-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1

module Main (main) where

import Control.DeepSeq (NFData (rnf))
import Criterion.Main
import Data.Word (Word8)
import System.IO (hSetEncoding, stdout, utf8)
import Progmasoft.Tala.AST
import Progmasoft.Tala.Core
import Progmasoft.Tala.Core.CorePrep
import Progmasoft.Tala.Core.CorePrep.Verifier
import Progmasoft.Tala.Core.CorePrep.Wire
import Progmasoft.Tala.Core.Optimizer
import Progmasoft.Tala.Core.Verifier
import Progmasoft.Tala.Core.Wire

-- Production IR intentionally does not carry evaluation-strategy instances.
-- Fixture owners force the complete derived representation before Criterion
-- starts sampling without adding benchmark-only instances to compiler types.
newtype CoreModules = CoreModules [(Int, CoreModule)]

newtype CorePrepModules = CorePrepModules [(Int, CorePrepModule)]

instance NFData CoreModules where
    rnf (CoreModules values) = rnf (show values)

instance NFData CorePrepModules where
    rnf (CorePrepModules values) = rnf (show values)

main :: IO ()
main = do
    -- Criterion uses the microsecond symbol in its progress output. Windows can
    -- otherwise inherit a legacy console encoding and abort after a successful
    -- sample, so the harness owns its output encoding explicitly.
    hSetEncoding stdout utf8
    defaultMain
        [ bgroup
            "Core"
            [ env (pure (CoreModules (fixtures sizes))) $ \modules ->
                bgroup "Verify" [benchAt size verifyDigest (coreModuleAt size modules) | size <- sizes]
            , env (pure (CoreModules (fixtures sizes))) $ \modules ->
                bgroup "Encode" [benchAt size encodeDigest (coreModuleAt size modules) | size <- sizes]
            , env (traverse encodedFixture sizes) $ \documents ->
                bgroup "Decode" [benchAt size decodeDigest (documentAt size documents) | size <- sizes]
            , env (pure (CoreModules (inlineFixtures inlineSizes))) $ \modules ->
                bgroup "InlineLinearBody" [benchAt size optimizeDigest (coreModuleAt size modules) | size <- inlineSizes]
            , env (pure (CoreModules (floatingFixtures floatingSizes))) $ \modules ->
                bgroup "ConstantFoldFloating" [benchAt size floatingOptimizeDigest (coreModuleAt size modules) | size <- floatingSizes]
            , env (pure (CoreModules (integerFixtures integerSizes))) $ \modules ->
                bgroup "ConstantFoldInteger" [benchAt size integerOptimizeDigest (coreModuleAt size modules) | size <- integerSizes]
            , env (pure (CoreModules (guardedDivisionFixtures flowSizes))) $ \modules ->
                bgroup "GuardedIntegerEffects" [benchAt size integerOptimizeDigest (coreModuleAt size modules) | size <- flowSizes]
            , env (pure (CoreModules (contradictoryPathFixtures flowSizes))) $ \modules ->
                bgroup "ContradictoryIntegerPaths" [benchAt size integerOptimizeDigest (coreModuleAt size modules) | size <- flowSizes]
            , env (pure (CoreModules (loopFixtures flowSizes))) $ \modules ->
                bgroup "LoopIntegerFacts" [benchAt size integerOptimizeDigest (coreModuleAt size modules) | size <- flowSizes]
            , env (verifiedFixtures makeNestedLoopModule loopDepths) $ \modules ->
                bgroup "NestedLoopIntegerFacts" [benchAt size integerOptimizeDigest (coreModuleAt size modules) | size <- loopDepths]
            , env (verifiedFixtures makeChainModule chainSizes) $ \modules ->
                bgroup "EncodeChain" [benchAt size encodeDigest (coreModuleAt size modules) | size <- chainSizes]
            ]
        , bgroup
            "CorePrep"
            [ env (pure (CoreModules (preparedFixtures sizes))) $ \modules ->
                bgroup "Prepare" [benchAt size prepareDigest (coreModuleAt size modules) | size <- sizes]
            , env (pure (CoreModules (loopFixtures sizes))) $ \modules ->
                bgroup "PrepareLoops" [benchAt size prepareDigest (coreModuleAt size modules) | size <- sizes]
            , env (CorePrepModules <$> traverse preparedFixture sizes) $ \modules ->
                bgroup "Verify" [benchAt size verifyPrepDigest (corePrepModuleAt size modules) | size <- sizes]
            , env (CorePrepModules <$> traverse preparedFixture sizes) $ \modules ->
                bgroup "Encode" [benchAt size encodePrepDigest (corePrepModuleAt size modules) | size <- sizes]
            , env (traverse preparedDocument sizes) $ \documents ->
                bgroup "Decode" [benchAt size decodePrepDigest (documentAt size documents) | size <- sizes]
            , env (verifiedFixtures makeChainModule chainSizes) $ \modules ->
                bgroup "PrepareChain" [benchAt size prepareDigest (coreModuleAt size modules) | size <- chainSizes]
            , env (verifiedFixtures makeSequenceModule chainSizes) $ \modules ->
                bgroup "PrepareSequence" [benchAt size prepareDigest (coreModuleAt size modules) | size <- chainSizes]
            , env (CorePrepModules <$> traverse (preparedFrom makeChainModule) chainSizes) $ \modules ->
                bgroup "VerifyChain" [benchAt size verifyPrepDigest (corePrepModuleAt size modules) | size <- chainSizes]
            , env (CorePrepModules <$> traverse (preparedFrom makeSequenceModule) chainSizes) $ \modules ->
                bgroup "VerifySequence" [benchAt size verifyPrepDigest (corePrepModuleAt size modules) | size <- chainSizes]
            ]
        ]
    where
        sizes = [8, 32, 128, 512]
        inlineSizes = [8, 32, 128, 256]
        floatingSizes = [8, 32, 128, 512]
        integerSizes = [8, 32, 128, 512, 1024, 2048]
        flowSizes = [8, 32, 128, 512, 1024]
        -- Levels of loops nested in each other. The loop analysis once
        -- repeated the analysis of an inner loop for every pass over the
        -- loop around it, so each level multiplied the time.
        loopDepths = [2, 4, 8, 12, 16]
        -- Links of an `else if` chain and statements of a sequence of `if`
        -- statements. Each doubling should double the time.
        chainSizes = [256, 512, 1024, 2048]

benchAt :: Int -> (a -> Int) -> a -> Benchmark
benchAt size measure input = bench (show size) (whnf measure input)

-- The size key selects a stable value owned by one Criterion environment. This
-- keeps fixture construction out of the timed expression without relying on
-- global CAF evaluation order.
moduleAt :: Int -> [(Int, a)] -> a
moduleAt requested values = case lookup requested values of
    Just value -> value
    Nothing -> error "benchmark fixture size was not prepared"

coreModuleAt :: Int -> CoreModules -> CoreModule
coreModuleAt requested (CoreModules values) = moduleAt requested values

corePrepModuleAt :: Int -> CorePrepModules -> CorePrepModule
corePrepModuleAt requested (CorePrepModules values) = moduleAt requested values

documentAt :: Int -> [(Int, [a])] -> [a]
documentAt = moduleAt

fixtures :: [Int] -> [(Int, CoreModule)]
fixtures = map (\size -> (size, makeCoreModule size))

preparedFixtures :: [Int] -> [(Int, CoreModule)]
preparedFixtures = fixtures

{- | This fixture isolates Core-to-CorePrep cost for structured loops, including
nested conditionals and explicit transfers in the loop body.
-}
loopFixtures :: [Int] -> [(Int, CoreModule)]
loopFixtures = map (\size -> (size, makeLoopModule size))

makeLoopModule :: Int -> CoreModule
makeLoopModule count =
    CoreModule (QualifiedName [Identifier "LoopBenchmark"]) (map makeLoopFunction [0 .. count - 1])
    where
        makeLoopFunction index =
            let base = index * 4 + 1
                functionName = name base "LoopFunction"
                valueName = name (base + 1) "value"
                value = CoreVariable valueName intType
                lessThan bound = CorePrimitive CoreLessThan [value, integer bound] boolType
                increment = CorePrimitive CoreAdd [value, integer 1] intType
                skippedValue = CorePrimitive CoreEqual [value, integer 2] boolType
                stoppedValue = CorePrimitive CoreEqual [value, integer 7] boolType
                body =
                    [ CoreBind (CoreBinding valueName intType True (integer 0))
                    , CoreWhile
                        (lessThan 5)
                        [ CoreAssign valueName increment
                        , CoreIf skippedValue [CoreContinue] []
                        ]
                    , CoreDoWhile
                        [ CoreAssign valueName increment
                        , CoreIf stoppedValue [CoreBreak] []
                        ]
                        (lessThan 9)
                    , CoreFor
                        (lessThan 12)
                        [ CoreAssign valueName increment
                        , CoreIf skippedValue [CoreContinue] []
                        ]
                        [CoreAssign valueName increment]
                    , CoreReturn value
                    ]
             in CoreFunction functionName [] intType body

{- | Fixtures that must verify: a benchmark of a rejected module would time
the rejection.
-}
verifiedFixtures :: (Int -> CoreModule) -> [Int] -> IO CoreModules
verifiedFixtures make requested = CoreModules <$> traverse verified requested
    where
        verified size = case verifyCore (make size) of
            Left diagnostics -> fail (show diagnostics)
            Right value -> pure (size, value)

preparedFrom :: (Int -> CoreModule) -> Int -> IO (Int, CorePrepModule)
preparedFrom make size = case prepareCore (make size) of
    Left diagnostics -> fail (show diagnostics)
    Right value -> case verifyCorePrep value of
        Left diagnostics -> fail (show diagnostics)
        Right verified -> pure (size, verified)

{- | Loops nested to the given depth, each counting to a parameter, around
one statement: the workload of the loop fixed point of the integer analysis.
-}
makeNestedLoopModule :: Int -> CoreModule
makeNestedLoopModule depth =
    CoreModule
        (QualifiedName [Identifier "NestedLoopBenchmark"])
        [ CoreFunction
            (name 1 "Count")
            [(limitName, intType)]
            intType
            (CoreBind (CoreBinding totalName intType True (integer 0)) : loopAt 0 ++ [CoreReturn total])
        ]
    where
        limitName = name 2 "limit"
        totalName = name 3 "total"
        total = CoreVariable totalName intType
        counterName level = name (4 + level) "counter"
        counter level = CoreVariable (counterName level) intType
        increment value = CorePrimitive CoreAdd [value, integer 1] intType
        loopAt level
            | level == depth = [CoreAssign totalName (increment total)]
            | otherwise =
                [ CoreBind (CoreBinding (counterName level) intType True (integer 0))
                , CoreWhile
                    (CorePrimitive CoreLessThan [counter level, CoreVariable limitName intType] boolType)
                    (CoreAssign (counterName level) (increment (counter level)) : loopAt (level + 1))
                ]

{- | An `else if` chain: every false branch holds the next link, so the
statements nest as deep as the chain is long.
-}
makeChainModule :: Int -> CoreModule
makeChainModule links =
    CoreModule
        (QualifiedName [Identifier "ChainBenchmark"])
        [CoreFunction (name 1 "Pick") [(valueName, intType)] intType (chainFrom 0)]
    where
        valueName = name 2 "value"
        chainFrom index
            | index == links = [CoreReturn (integer 0)]
            | otherwise =
                [ CoreIf
                    (CorePrimitive CoreEqual [CoreVariable valueName intType, integer (toInteger index)] boolType)
                    [CoreReturn (integer (toInteger index * 3 + 1))]
                    (chainFrom (index + 1))
                ]

-- | The same tests as a sequence of `if` statements, which do not nest.
makeSequenceModule :: Int -> CoreModule
makeSequenceModule count =
    CoreModule
        (QualifiedName [Identifier "SequenceBenchmark"])
        [ CoreFunction
            (name 1 "Sum")
            [(valueName, intType)]
            intType
            (CoreBind (CoreBinding totalName intType True (integer 0)) : map test [0 .. count - 1] ++ [CoreReturn total])
        ]
    where
        valueName = name 2 "value"
        totalName = name 3 "total"
        total = CoreVariable totalName intType
        test index =
            CoreIf
                (CorePrimitive CoreEqual [CoreVariable valueName intType, integer (toInteger index)] boolType)
                [CoreAssign totalName (CorePrimitive CoreAdd [total, integer (toInteger index)] intType)]
                []

inlineFixtures :: [Int] -> [(Int, CoreModule)]
inlineFixtures = map (\size -> (size, makeInlineModule size))

-- The timed optimizer receives a verified nested expression containing only
-- binary64 literals. Fixture construction is kept in Criterion's environment;
-- the varying size measures rational parsing, target rounding, shortest
-- round-trip rendering, and fixed-point stability as one real Core workload.
floatingFixtures :: [Int] -> [(Int, CoreModule)]
floatingFixtures = map (\size -> (size, makeFloatingModule size))

-- This workload keeps construction outside Criterion's sample and drives the
-- real fixed-point optimizer through integer power, shifts, bitwise operations,
-- and addition at a 128-bit destination width.
integerFixtures :: [Int] -> [(Int, CoreModule)]
integerFixtures = map (\size -> (size, makeIntegerModule size))

-- These fixtures target the forward abstract interpreter rather than constant
-- folding: every divide uses a variable, and safety is available only on a
-- condition edge. Keeping construction in Criterion's environment makes the
-- timed region reflect the production optimizer's proof and effect passes.
guardedDivisionFixtures :: [Int] -> [(Int, CoreModule)]
guardedDivisionFixtures = map (\size -> (size, makeGuardedDivisionModule size))

contradictoryPathFixtures :: [Int] -> [(Int, CoreModule)]
contradictoryPathFixtures = map (\size -> (size, makeContradictoryPathModule size))

makeGuardedDivisionModule :: Int -> CoreModule
makeGuardedDivisionModule size =
    CoreModule
        (QualifiedName [Identifier "Core", Identifier "GuardedIntegerFlowBenchmarks"])
        [ CoreFunction
            functionName
            [(divisorName, integerType)]
            integerType
            [ CoreIf
                (CorePrimitive CoreNotEqual [divisor, typedInteger 0] boolType)
                (replicate size (CoreEvaluate division))
                [CoreReturn (typedInteger 0)]
            , CoreReturn (typedInteger 1)
            ]
        ]
    where
        functionName = resolvedName 1 "Evaluate"
        divisorName = resolvedName 2 "divisor"
        divisor = CoreVariable divisorName integerType
        integerType = namedType "int"
        typedInteger value = CoreLiteral (CoreInteger value) integerType
        division = CorePrimitive CoreDivide [typedInteger 42, divisor] integerType

makeContradictoryPathModule :: Int -> CoreModule
makeContradictoryPathModule size =
    CoreModule
        (QualifiedName [Identifier "Core", Identifier "ContradictoryIntegerFlowBenchmarks"])
        [ CoreFunction
            functionName
            [(valueName, integerType)]
            integerType
            [ CoreIf condition (replicate size (CoreEvaluate impossibleDivision)) [CoreReturn (typedInteger 0)]
            , CoreReturn (typedInteger 1)
            ]
        ]
    where
        functionName = resolvedName 1 "Evaluate"
        valueName = resolvedName 2 "value"
        integerType = namedType "int"
        value = CoreVariable valueName integerType
        typedInteger number = CoreLiteral (CoreInteger number) integerType
        condition =
            CorePrimitive
                CoreLogicalAnd
                [ CorePrimitive CoreGreaterThan [value, typedInteger 11] boolType
                , CorePrimitive CoreLessThan [value, typedInteger 12] boolType
                ]
                boolType
        impossibleDivision = CorePrimitive CoreDivide [typedInteger 42, value] integerType

resolvedName :: Int -> String -> ResolvedName
resolvedName symbol spelling = ResolvedName (SymbolId symbol) (Identifier spelling)

makeIntegerModule :: Int -> CoreModule
makeIntegerModule size =
    CoreModule
        (QualifiedName [Identifier "Core", Identifier "IntegerBenchmarks"])
        [ CoreFunction
            (ResolvedName (SymbolId 1) (Identifier "Evaluate"))
            []
            integerType
            [CoreReturn expression]
        ]
    where
        integerType = namedType "ulongint"
        expression = foldl appendIntegerOperations (typedInteger 0) [0 .. size - 1]
        appendIntegerOperations accumulated index =
            let base = toInteger (2 + index `mod` 3)
                power = CorePrimitive CorePower [typedInteger base, typedInteger 7] integerType
                shifted = CorePrimitive CoreShiftLeft [power, typedInteger (toInteger (index `mod` 8))] integerType
                mixed = CorePrimitive CoreBitwiseXor [accumulated, shifted] integerType
             in CorePrimitive CoreAdd [mixed, typedInteger 1] integerType
        typedInteger value = CoreLiteral (CoreInteger value) integerType

makeFloatingModule :: Int -> CoreModule
makeFloatingModule size =
    CoreModule
        (QualifiedName [Identifier "Core", Identifier "FloatingBenchmarks"])
        [ CoreFunction
            (ResolvedName (SymbolId 1) (Identifier "Evaluate"))
            []
            (namedType "float")
            [CoreReturn (foldl addLiteral (CoreLiteral (CoreFloating "0.1") (namedType "float")) [1 .. size])]
        ]
    where
        addLiteral accumulated _ =
            CorePrimitive
                CoreAdd
                [accumulated, CoreLiteral (CoreFloating "0.1") (namedType "float")]
                (namedType "float")

encodedFixture :: Int -> IO (Int, [Word8])
encodedFixture size = case verifyCore (makeCoreModule size) of
    Left diagnostics -> fail (show diagnostics)
    Right verified -> case encodeCore defaultCoreWireLimits verified of
        Left failure -> fail (show failure)
        Right bytes -> pure (size, bytes)

preparedFixture :: Int -> IO (Int, CorePrepModule)
preparedFixture size = case prepareCore (makeCoreModule size) of
    Left diagnostics -> fail (show diagnostics)
    Right value -> case verifyCorePrep value of
        Left diagnostics -> fail (show diagnostics)
        Right verified -> pure (size, verified)

preparedDocument :: Int -> IO (Int, [Word8])
preparedDocument size = do
    (_, prepared) <- preparedFixture size
    case encodeCorePrep prepared of
        Left failure -> fail (show failure)
        Right bytes -> pure (size, bytes)

verifyDigest :: CoreModule -> Int
verifyDigest value = either length coreDigest (verifyCore value)

encodeDigest :: CoreModule -> Int
encodeDigest value = either (length . show) length (encodeCore defaultCoreWireLimits value)

decodeDigest :: [Word8] -> Int
decodeDigest bytes = either (length . show) coreDigest (decodeCore defaultCoreWireLimits bytes)

-- Measure the production verifier/effect/inliner fixed point. Fixture creation
-- remains in Criterion's environment and is not charged to the sample.
optimizeDigest :: CoreModule -> Int
optimizeDigest value = case optimizeCoreWith inlineBenchmarkOptions value of
    Left diagnostics -> length diagnostics
    Right result ->
        coreDigest (optimizedCore result)
            + optimizationIterations result
            + sum (map inlineRewrittenCalls (optimizationInlineReports result))

floatingOptimizeDigest :: CoreModule -> Int
floatingOptimizeDigest value = either length (coreDigest . optimizedCore) (optimizeCoreWith defaultOptimizerOptions value)

integerOptimizeDigest :: CoreModule -> Int
integerOptimizeDigest value = either length (coreDigest . optimizedCore) (optimizeCoreWith defaultOptimizerOptions value)

inlineBenchmarkOptions :: OptimizerOptions
inlineBenchmarkOptions =
    defaultOptimizerOptions
        { optimizerConstantPropagation = False
        , optimizerControlFlowSimplification = False
        , optimizerDeadCodeElimination = False
        }

prepareDigest :: CoreModule -> Int
prepareDigest value = either length corePrepDigest (prepareCore value)

verifyPrepDigest :: CorePrepModule -> Int
verifyPrepDigest value = either length corePrepDigest (verifyCorePrep value)

encodePrepDigest :: CorePrepModule -> Int
encodePrepDigest value = either (length . show) length (encodeCorePrep value)

decodePrepDigest :: [Word8] -> Int
decodePrepDigest bytes = either (length . show) corePrepDigest (decodeCorePrep bytes)

-- A structural digest forces every recursive container without allocating the
-- large intermediate String produced by show. The traversal is intentionally
-- simple and shared by operations whose result is an IR value.
coreDigest :: CoreModule -> Int
coreDigest value = sum (map functionDigest (coreModuleFunctions value))

functionDigest :: CoreFunction -> Int
functionDigest value =
    1
        + length (coreFunctionParameters value)
        + sum (map statementDigest (coreFunctionBody value))

statementDigest :: CoreStatement -> Int
statementDigest statement = case statement of
    CoreBind binding -> 1 + expressionDigest (coreBindingValue binding)
    CoreAssign _ expression -> 1 + expressionDigest expression
    CoreReturn expression -> 1 + expressionDigest expression
    CoreIf condition whenTrue whenFalse ->
        1 + expressionDigest condition + sum (map statementDigest whenTrue) + sum (map statementDigest whenFalse)
    CoreEvaluate expression -> 1 + expressionDigest expression
    CoreWhile condition body -> 1 + expressionDigest condition + sum (map statementDigest body)
    CoreDoWhile body condition -> 1 + sum (map statementDigest body) + expressionDigest condition
    CoreFor condition body update ->
        1
            + expressionDigest condition
            + sum (map statementDigest body)
            + sum (map statementDigest update)
    CoreBreak -> 1
    CoreContinue -> 1

expressionDigest :: CoreExpression -> Int
expressionDigest expression = case expression of
    CoreVariable _ _ -> 1
    CoreLiteral _ _ -> 1
    CoreApply callee arguments _ -> 1 + expressionDigest callee + sum (map expressionDigest arguments)
    CorePrimitive _ arguments _ -> 1 + sum (map expressionDigest arguments)
    CoreLet _ _ value body _ -> 1 + expressionDigest value + expressionDigest body
    CoreConditional condition whenTrue whenFalse _ ->
        1 + sum (map expressionDigest [condition, whenTrue, whenFalse])
    CoreClosure captures parameters _ body _ ->
        1
            + length parameters
            + sum (map captureDigest captures)
            + sum (map statementDigest body)

captureDigest :: CoreCapture -> Int
captureDigest capture = 1 + expressionDigest (coreCaptureValue capture)

corePrepDigest :: CorePrepModule -> Int
corePrepDigest value = sum (map prepFunctionDigest (corePrepModuleFunctions value))

prepFunctionDigest :: CorePrepFunction -> Int
prepFunctionDigest value =
    1
        + length (corePrepFunctionParameters value)
        + sum (map blockDigest (corePrepFunctionBlocks value))

blockDigest :: CorePrepBlock -> Int
blockDigest value =
    1
        + sum (map instructionDigest (corePrepBlockInstructions value))
        + terminatorDigest (corePrepBlockTerminator value)

instructionDigest :: CorePrepInstruction -> Int
instructionDigest instruction = case instruction of
    CorePrepBind _ _ _ operation -> 1 + operationDigest operation
    CorePrepAssign _ atom -> 1 + atomDigest atom
    CorePrepEvaluate operation -> 1 + operationDigest operation

operationDigest :: CorePrepOperation -> Int
operationDigest operation = case operation of
    CorePrepCopy atom -> 1 + atomDigest atom
    CorePrepCall callee arguments -> 1 + atomDigest callee + sum (map atomDigest arguments)
    CorePrepPrimitive _ arguments -> 1 + sum (map atomDigest arguments)
    CorePrepMakeClosure _ captures -> 1 + sum (map prepCaptureDigest captures)

prepCaptureDigest :: CorePrepCapture -> Int
prepCaptureDigest (CorePrepCapture _ _ _ atom) = 1 + atomDigest atom

atomDigest :: CorePrepAtom -> Int
atomDigest atom = case atom of
    CorePrepVariable _ _ -> 1
    CorePrepLiteral _ _ -> 1

terminatorDigest :: CorePrepTerminator -> Int
terminatorDigest terminator = case terminator of
    CorePrepReturn atom -> 1 + atomDigest atom
    CorePrepBranch atom _ _ -> 1 + atomDigest atom
    CorePrepJump _ -> 1
    CorePrepUnreachable -> 1

makeCoreModule :: Int -> CoreModule
makeCoreModule count = CoreModule (QualifiedName [Identifier "Benchmark"]) (map makeFunction [0 .. count - 1])

-- Each call expands a two-binding linear helper. Function and local symbols are
-- globally disjoint so the benchmark measures fresh allocation and cloning,
-- not malformed-input rejection.
makeInlineModule :: Int -> CoreModule
makeInlineModule count =
    CoreModule
        (QualifiedName [Identifier "InlineBenchmark"])
        (inlineHelper : map makeCaller [0 .. count - 1])
    where
        helperName = name 1 "ScaleAndBias"
        parameterName = name 2 "input"
        firstName = name 3 "scaled"
        secondName = name 4 "biased"
        inlineHelper =
            CoreFunction
                helperName
                [(parameterName, intType)]
                intType
                [ CoreBind
                    ( CoreBinding
                        firstName
                        intType
                        False
                        (CorePrimitive CoreMultiply [CoreVariable parameterName intType, integer 2] intType)
                    )
                , CoreBind
                    ( CoreBinding
                        secondName
                        intType
                        False
                        (CorePrimitive CoreAdd [CoreVariable firstName intType, integer 1] intType)
                    )
                , CoreReturn (CoreVariable secondName intType)
                ]
        makeCaller index =
            let base = index * 3 + 100
                argumentName = name (base + 1) "argument"
                callerName = name (base + 2) "Caller"
                argument = CorePrimitive CoreAdd [integer (toInteger index), integer 7] intType
                applied =
                    CoreApply
                        (CoreVariable helperName (FunctionType [intType] intType))
                        [argument]
                        intType
             in CoreFunction
                    callerName
                    []
                    intType
                    [ CoreBind (CoreBinding argumentName intType False applied)
                    , CoreReturn (CoreVariable argumentName intType)
                    ]

makeFunction :: Int -> CoreFunction
makeFunction index =
    CoreFunction
        (name base "Function")
        []
        intType
        [ CoreBind (CoreBinding valueName intType True sumValue)
        , CoreIf comparison [CoreAssign valueName (integer 2)] [CoreAssign valueName (integer 0)]
        , CoreReturn (CoreVariable valueName intType)
        ]
    where
        base = index * 4 + 1
        valueName = name (base + 1) "value"
        sumValue = CorePrimitive CoreAdd [integer (toInteger index), integer 1] intType
        comparison = CorePrimitive CoreGreaterEqual [CoreVariable valueName intType, integer 1] boolType

name :: Int -> String -> ResolvedName
name identifier spelling = ResolvedName (SymbolId identifier) (Identifier spelling)

integer :: Integer -> CoreExpression
integer value = CoreLiteral (CoreInteger value) intType