haskell - Sum 类型函数参数的 GHC 调用约定

标签 haskell ghc

GHC 是否在将 sum 类型传递给函数时对其进行解包?例如,假设我们有以下类型:

data Foo
  = Foo1 {-# UNPACK #-} !Int {-# UNPACK #-} !Word
  | Foo2 {-# UNPACK #-} !Int
  | Foo3 {-# UNPACK #-} !Word

然后我定义了一个严格的函数 Foo争论:
consumeFoo :: Foo -> Int
consumeFoo x = case x of ...

在运行时,当我调用 consumeFoo ,我可以期待会发生什么? GHC calling convention是在寄存器中传递参数(或者一旦有太多就在堆栈上)。我可以看到参数传递的两种方式:
  • 指向 Foo 的指针在堆上作为一个参数传入。
  • Foo 的三参数表示使用,一个参数表示使用的数据构造函数,另外两个表示可能的 IntWord数据构造函数中的值。

  • 我更喜欢第二种表示,但我不知道它是否真的发生了。我知道 UnpackedSumTypes登陆 GHC 8.2,但不清楚它是否符合我的要求。如果我改为将函数编写为:
    consumeFooAlt :: (# (# Int#, Word# #) | Int# | Word# #) -> Int
    

    然后我希望评估(2)会发生什么。和 Unpacking section未打包的总和页面表明我也可以这样做:
    data Wrap = Wrap {-# UNPACK #-} !Foo
    consumeFooAlt2 :: Wrap -> Int
    

    我认为,这也应该有我想要的代表。

    所以我的问题是,在不使用包装类型或原始解包总和的情况下,当我将总和作为参数传递给函数时,如何保证总和被解包到寄存器(或堆栈上)?如果可能的话,是 GHC 8.0 已经可以做到的事情,还是只能在 GHC 8.2 中提供的事情?

    最佳答案

    第一:保证优化和 GHC 不能很好地混合。由于级别很高,很难预测 GHC 在每种情况下都会生成的代码。唯一可以确定的方法是查看核心。如果您正在使用 GHC 开发一个对性能非常依赖的应用程序,那么您需要熟悉 Core I。

    我不知道 GHC 中的任何优化都完全符合您的描述。这是一个示例程序:

    module Test where
    
    data Sum = A {-# UNPACK #-} !Int | B {-# UNPACK #-} !Int
    
    consumeSum :: Sum -> Int
    consumeSum x = case x of
      A y -> y + 1
      B y -> y + 2
    
    {-# NOINLINE consumeSumNoinline #-}
    consumeSumNoinline = consumeSum
    
    {-# INLINE produceSumInline #-}
    produceSumInline :: Int -> Sum
    produceSumInline x = if x == 0 then A x else B x
    
    {-# NOINLINE produceSumNoinline #-}
    produceSumNoinline :: Int -> Sum
    produceSumNoinline x = if x == 0 then A x else B x
    
    test :: Int -> Int
    --test x = consumeSum (produceSumInline x)
    test x = consumeSumNoinline (produceSumNoinline x)
    

    让我们先看看如果我们不内联 consumeSum 会发生什么也不是 produceSum .这里是核心:
    test :: Int -> Int
    test = \ (x :: Int) -> consumeSumNoinline (produceSumNoinline x)
    

    (使用 ghc-core test.hs -- -dsuppress-unfoldings -dsuppress-idinfo -dsuppress-module-prefixes -dsuppress-uniques 制作)

    在这里,我们可以看到 GHC(在本例中为 8.0)没有拆箱作为函数参数传递的 sum 类型。如果我们内联 consumeSum,则没有任何变化或 produceSum .

    但是,如果我们将两者都内联,则会生成以下代码:
    test :: Int -> Int
    test =
      \ (x :: Int) ->
        case x of _ { I# x1 ->
        case x1 of wild1 {
          __DEFAULT -> I# (+# wild1 2#);
          0# -> lvl1
        }
        }
    

    这里发生的是,通过内联,GHC 最终得到:
    \x -> case (if x == 0 then A x else B x) of
       A y -> y + 1
       B y -> y + 2
    

    通过 case-of-case ( if 只是一个特殊的 case ) 变成:
    \x -> if x == 0 then case (A x) of ...  else case (B x) of ...
    

    现在这是一个已知构造函数的情况,因此 GHC 可以在编译时减少这种情况,最终得到:
    \x -> if x == 0 then x + 1 else x + 2
    

    所以它完全消除了构造函数。

    总之,我认为 GHC 在 8.2 版本之前没有任何“未装箱总和”类型的概念,这也适用于函数参数。获得“拆箱”总和的唯一方法是通过内联完全消除构造函数。

    关于haskell - Sum 类型函数参数的 GHC 调用约定,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41265229/

    相关文章:

    haskell - 如何判断 GHC 是否真正推断出严格性?

    java - 调用从 Java 返回字符串的 Haskell 函数

    haskell - ghc 可以将某些指定的警告视为错误而将其他警告视为警告吗?

    haskell - Haskell、Ocaml、Javascript 中的语句与表达式

    haskell - inline-c 和 language-c-inline 有什么区别?

    performance - 创建集合中所有元素对的 Data.Set 的最有效方法是什么?

    haskell - 如果我尝试运行简单的 binTree,GHCi 就会卡住

    haskell - 对于某些 TVar 具有部分原子性的 STM

    haskell - 哪些语言扩展可以写入 "class A (B c) => D c where ..."?这个类型类声明的含义是什么?

    function - GHC API : Find all functions (and their types) in scope