packages feed

hhlo-0.11.0.0: src/HHLO/ModuleBuilder.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE OverloadedStrings #-}

-- | Auto-argument module construction.
--
-- Build StableHLO modules without manual 'FuncArg' declarations or 'natVal'
-- boilerplate. The input/output arity is specified via 'TypeApplications';
-- GHC selects the matching 'ModuleBuilder' instance.
--
-- > kmeansModule = buildModule @2 @1 "kmeans" $ \dat initC -> do
-- >     lloyd dat initC
--
-- ⚠️ 'TypeApplications' are mandatory: you must write @buildModule @nIn @nOut@.
-- ⚠️ Lambda parameters must be used in operations so GHC can infer their
--    'Tensor' type. Unused parameters require explicit type annotations.
module HHLO.ModuleBuilder
    ( buildModule
    , ModuleBuilder
    ) where

import GHC.TypeLits
import Data.Proxy
import Data.Text (Text)

import HHLO.Core.Types
import HHLO.IR.AST (Module, FuncArg(..))
import HHLO.IR.Builder

-- | Typeclass dispatching module construction by input and output arity.
-- Each instance auto-generates 'FuncArg' declarations and wires up 'arg' calls.
class ModuleBuilder (nIn :: Nat) (nOut :: Nat) f where
    buildModule' :: Text -> f -> Module

-- ---------------------------------------------------------------------------
-- 1 input, 1 output
-- ---------------------------------------------------------------------------

instance ( KnownShape s1, KnownDType d1
         , KnownShape sr, KnownDType dr
         ) => ModuleBuilder 1 1 (Tensor s1 d1 -> Builder (Tensor sr dr)) where
    buildModule' name f =
        let arg0 = FuncArg "arg0" (tensorType (Proxy @s1) (Proxy @d1))
            builder = do
                t0 <- arg @s1 @d1
                f t0
        in moduleFromBuilder @sr @dr name [arg0] builder

-- ---------------------------------------------------------------------------
-- 1 input, 2 outputs
-- ---------------------------------------------------------------------------

instance ( KnownShape s1, KnownDType d1
         , KnownShape sr1, KnownDType dr1
         , KnownShape sr2, KnownDType dr2
         ) => ModuleBuilder 1 2 (Tensor s1 d1 -> Builder (Tuple2 sr1 dr1 sr2 dr2)) where
    buildModule' name f =
        let arg0 = FuncArg "arg0" (tensorType (Proxy @s1) (Proxy @d1))
            builder = do
                t0 <- arg @s1 @d1
                f t0
        in moduleFromBuilder2 name [arg0] builder

-- ---------------------------------------------------------------------------
-- 1 input, 3 outputs
-- ---------------------------------------------------------------------------

instance ( KnownShape s1, KnownDType d1
         , KnownShape sr1, KnownDType dr1
         , KnownShape sr2, KnownDType dr2
         , KnownShape sr3, KnownDType dr3
         ) => ModuleBuilder 1 3 (Tensor s1 d1 -> Builder (Tuple3 sr1 dr1 sr2 dr2 sr3 dr3)) where
    buildModule' name f =
        let arg0 = FuncArg "arg0" (tensorType (Proxy @s1) (Proxy @d1))
            builder = do
                t0 <- arg @s1 @d1
                f t0
        in moduleFromBuilder3 name [arg0] builder

-- ---------------------------------------------------------------------------
-- 2 inputs, 1 output
-- ---------------------------------------------------------------------------

instance ( KnownShape s1, KnownDType d1
         , KnownShape s2, KnownDType d2
         , KnownShape sr, KnownDType dr
         ) => ModuleBuilder 2 1 (Tensor s1 d1 -> Tensor s2 d2 -> Builder (Tensor sr dr)) where
    buildModule' name f =
        let arg0 = FuncArg "arg0" (tensorType (Proxy @s1) (Proxy @d1))
            arg1 = FuncArg "arg1" (tensorType (Proxy @s2) (Proxy @d2))
            builder = do
                t0 <- arg @s1 @d1
                t1 <- arg @s2 @d2
                f t0 t1
        in moduleFromBuilder @sr @dr name [arg0, arg1] builder

-- ---------------------------------------------------------------------------
-- 2 inputs, 2 outputs
-- ---------------------------------------------------------------------------

instance ( KnownShape s1, KnownDType d1
         , KnownShape s2, KnownDType d2
         , KnownShape sr1, KnownDType dr1
         , KnownShape sr2, KnownDType dr2
         ) => ModuleBuilder 2 2 (Tensor s1 d1 -> Tensor s2 d2 -> Builder (Tuple2 sr1 dr1 sr2 dr2)) where
    buildModule' name f =
        let arg0 = FuncArg "arg0" (tensorType (Proxy @s1) (Proxy @d1))
            arg1 = FuncArg "arg1" (tensorType (Proxy @s2) (Proxy @d2))
            builder = do
                t0 <- arg @s1 @d1
                t1 <- arg @s2 @d2
                f t0 t1
        in moduleFromBuilder2 name [arg0, arg1] builder

-- ---------------------------------------------------------------------------
-- 2 inputs, 3 outputs
-- ---------------------------------------------------------------------------

instance ( KnownShape s1, KnownDType d1
         , KnownShape s2, KnownDType d2
         , KnownShape sr1, KnownDType dr1
         , KnownShape sr2, KnownDType dr2
         , KnownShape sr3, KnownDType dr3
         ) => ModuleBuilder 2 3 (Tensor s1 d1 -> Tensor s2 d2 -> Builder (Tuple3 sr1 dr1 sr2 dr2 sr3 dr3)) where
    buildModule' name f =
        let arg0 = FuncArg "arg0" (tensorType (Proxy @s1) (Proxy @d1))
            arg1 = FuncArg "arg1" (tensorType (Proxy @s2) (Proxy @d2))
            builder = do
                t0 <- arg @s1 @d1
                t1 <- arg @s2 @d2
                f t0 t1
        in moduleFromBuilder3 name [arg0, arg1] builder

-- ---------------------------------------------------------------------------
-- 3 inputs, 1 output
-- ---------------------------------------------------------------------------

instance ( KnownShape s1, KnownDType d1
         , KnownShape s2, KnownDType d2
         , KnownShape s3, KnownDType d3
         , KnownShape sr, KnownDType dr
         ) => ModuleBuilder 3 1 (Tensor s1 d1 -> Tensor s2 d2 -> Tensor s3 d3 -> Builder (Tensor sr dr)) where
    buildModule' name f =
        let arg0 = FuncArg "arg0" (tensorType (Proxy @s1) (Proxy @d1))
            arg1 = FuncArg "arg1" (tensorType (Proxy @s2) (Proxy @d2))
            arg2 = FuncArg "arg2" (tensorType (Proxy @s3) (Proxy @d3))
            builder = do
                t0 <- arg @s1 @d1
                t1 <- arg @s2 @d2
                t2 <- arg @s3 @d3
                f t0 t1 t2
        in moduleFromBuilder @sr @dr name [arg0, arg1, arg2] builder

-- ---------------------------------------------------------------------------
-- 3 inputs, 2 outputs
-- ---------------------------------------------------------------------------

instance ( KnownShape s1, KnownDType d1
         , KnownShape s2, KnownDType d2
         , KnownShape s3, KnownDType d3
         , KnownShape sr1, KnownDType dr1
         , KnownShape sr2, KnownDType dr2
         ) => ModuleBuilder 3 2 (Tensor s1 d1 -> Tensor s2 d2 -> Tensor s3 d3 -> Builder (Tuple2 sr1 dr1 sr2 dr2)) where
    buildModule' name f =
        let arg0 = FuncArg "arg0" (tensorType (Proxy @s1) (Proxy @d1))
            arg1 = FuncArg "arg1" (tensorType (Proxy @s2) (Proxy @d2))
            arg2 = FuncArg "arg2" (tensorType (Proxy @s3) (Proxy @d3))
            builder = do
                t0 <- arg @s1 @d1
                t1 <- arg @s2 @d2
                t2 <- arg @s3 @d3
                f t0 t1 t2
        in moduleFromBuilder2 name [arg0, arg1, arg2] builder

-- ---------------------------------------------------------------------------
-- 3 inputs, 3 outputs
-- ---------------------------------------------------------------------------

instance ( KnownShape s1, KnownDType d1
         , KnownShape s2, KnownDType d2
         , KnownShape s3, KnownDType d3
         , KnownShape sr1, KnownDType dr1
         , KnownShape sr2, KnownDType dr2
         , KnownShape sr3, KnownDType dr3
         ) => ModuleBuilder 3 3 (Tensor s1 d1 -> Tensor s2 d2 -> Tensor s3 d3 -> Builder (Tuple3 sr1 dr1 sr2 dr2 sr3 dr3)) where
    buildModule' name f =
        let arg0 = FuncArg "arg0" (tensorType (Proxy @s1) (Proxy @d1))
            arg1 = FuncArg "arg1" (tensorType (Proxy @s2) (Proxy @d2))
            arg2 = FuncArg "arg2" (tensorType (Proxy @s3) (Proxy @d3))
            builder = do
                t0 <- arg @s1 @d1
                t1 <- arg @s2 @d2
                t2 <- arg @s3 @d3
                f t0 t1 t2
        in moduleFromBuilder3 name [arg0, arg1, arg2] builder

-- ---------------------------------------------------------------------------
-- Polymorphic entry point
-- ---------------------------------------------------------------------------

-- | Build a 'Module' from a typed function with auto-generated 'FuncArg's.
--
-- Usage: @buildModule @inputs @outputs "name" $ \arg1 arg2 -> ...@
--
-- The @inputs@ and @outputs@ parameters are type-level 'Nat's specifying
-- the arity of the function. GHC selects the matching 'ModuleBuilder'
-- instance based on these numbers and the lambda's type.
--
-- The name argument is used as the function name in the generated MLIR.
-- For PJRT compatibility the function should be named @"main"@.
buildModule :: forall nIn nOut f. ModuleBuilder nIn nOut f => Text -> f -> Module
buildModule _name = buildModule' @nIn @nOut "main"