[英]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
實現如下:
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.