簡體   English   中英

在C和Haskell的相互遞歸中編譯尾調用優化

[英]Compiling Tail-Call Optimization In Mutual Recursion Across C and Haskell

我正在試驗Haskell中的外部函數接口。 我想實現一個簡單的測試,看看我是否可以進行相互遞歸。 所以,我創建了以下Haskell代碼:

module MutualRecursion where
import Data.Int

foreign import ccall countdownC::Int32->IO ()
foreign export ccall countdownHaskell::Int32->IO()

countdownHaskell::Int32->IO()
countdownHaskell n = print n >> if n > 0 then countdownC (pred n) else return ()

請注意,遞歸情況是對countdownC的調用,因此這應該是尾遞歸的。

在我的C代碼中,我有

#include <stdio.h>

#include "MutualRecursionHaskell_stub.h"

void countdownC(int count)
{
    printf("%d\n", count);
    if(count > 0)
        return countdownHaskell(count-1);
}

int main(int argc, char* argv[])
{
    hs_init(&argc, &argv);

    countdownHaskell(10000);

    hs_exit();
    return 0;
}

這同樣是尾遞歸。 那我就做了

MutualRecursion: MutualRecursionHaskell_stub
    ghc -O2 -no-hs-main MutualRecursionC.c MutualRecursionHaskell.o -o MutualRecursion
MutualRecursionHaskell_stub:
    ghc -O2 -c MutualRecursionHaskell.hs

並使用make MutualRecursion編譯。

並且......在運行時,它會在打印8991 就像確保gcc本身可以在相互遞歸中處理tco的測試一樣,我做到了

void countdownC2(int);

void countdownC(int count)
{
    printf("%d\n", count);
    if(count > 0)
        return countdownC2(count-1);
}

void countdownC2(int count)
{
    printf("%d\n", count);
    if(count > 0)
        return countdownC(count-1);
}

這工作得很好。 它也適用於C語言和Haskell中的單遞歸情況。

所以我的問題是,有沒有辦法向GHC表明對外部C函數的調用是尾遞歸的? 我假設堆棧幀確實來自從Haskell到C的調用,而不是相反,因為C代碼非常明顯地是函數調用的返回。

我相信跨語言的C-Haskell尾調用非常非常難以實現。

我不知道確切的細節,但C運行時和Haskell運行時有很大的不同。 據我所知,這種差異的主要因素是:

  • 不同范式:純粹的功能與命令式
  • 垃圾收集與手動內存管理
  • 懶惰的語義與嚴格的語義

鑒於這種差異,可能在語言邊界存活的優化種類幾乎為零。 理論上,也許可以發明一個特殊的C運行時和Haskell運行時,以便一些優化是可行的,但GHC和GCC並不是以這種方式設計的。

為了展示潛在差異的示例,假設我們有以下Haskell代碼

p :: Int -> Bool
p x = x==42

main = if p 42
       then putStrLn "A"     -- A
       else putStrLn "B"     -- B

一可能實施方案的main可能是以下幾點:

  • 在堆棧上推送A的地址
  • 在堆棧上推送B的地址
  • 42推到堆棧上
  • 跳到p
  • A :打印“A”,跳到最后
  • B :打印“B”,跳到最后

p實現如下:

  • p:從堆棧中彈出x
  • 從堆棧彈出b
  • 從堆棧彈出a
  • 測試x對42
  • 如果相等,跳轉到a
  • 跳到b

注意如何使用兩個返回地址調用p ,每個可能的結果一個。 這與C不同,C的標准實現僅使用一個返回地址。 跨越邊界時,編譯器必須考慮到這種差異並進行補償。

上面我也沒有說明當p的參數是thunk的情況時,為了保持簡單。 GHC分配器還可以觸發垃圾收集。

注意,上面的虛構實現過去實際上是由GHC(所謂的“推/輸”STG機器)使用的。 即使現在不再使用它,“eval / apply”STG機器也只是稍微靠近C運行時。 我甚至不確定使用常規C堆棧的GHC:我認為它沒有使用它自己的。

您可以查看GHC開發人員維基以查看血淋淋的詳細信息。

雖然我不是Haskel-C互操作的專家,但我不認為從C到Haskel的調用可以是一個直接的函數調用 - 它很可能必須通過中介來設置環境。 因此,您對haskel的調用實際上包括調用此中間人。 這個電話很可能是由gcc優化的。 但是從中間人到實際的Haskel例程的調用並沒有被完美地優化 - 所以我認為,這就是你正在處理的事情。 您可以檢查裝配輸出以確保。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM