在编译时使用类型提供程序加载和存储文件

Loading and storing a file at compile time with a type provider

我想在编译时加载一个(二进制)文件并将其存储在 Bytes 类型的顶层变量中:

module FileProvider

import Data.Bits
import Data.Bytes
import Data.Buffer

%default total

export
loadImage : String -> IO (Provider Bytes)
loadImage fileName = do
    Right file <- openFile fileName Read
      | Left err => pure (Error $ show err)
    Just buf <- newBuffer size
      | Nothing => pure (Error "allocation failed")
    readBufferFromFile file buf size
    Provide . dropPrefix 2 . pack <$> bufferData buf
  where
    size : Int
    size = 256 * 256 + 2

它似乎在 运行 时间正常工作:

module Main

import FileProvider
import Data.Bits
import Data.Bytes

main : IO ()
main = do
     Provide mem <- loadImage "ram.mem"
       | Error err => putStrLn err
     printLn $ length mem

但是,如果我在编译时尝试 运行 它,它会失败并显示一条提到 FFI 的神秘消息:

module Main

import FileProvider
import Data.Bits
import Data.Bytes

%language TypeProviders
%provide (mem : Bytes) with loadImage "ram.mem"

main : IO ()
main = do
     printLn $ length mem
$ idris -p bytes -o SOMain SOMain.idr 
Symbol "idris_newBuffer" not found
idris: user error (Could not call foreign function "idris_newBuffer" with args [65538])

这是怎么回事,我如何在编译时加载文件的内容?

idris_newbufferData.Buffer 使用的 C 函数。来自 docs to type providers:

If we want to call our foreign functions from interpreted code (such as the REPL or a type provider), we need to dynamically link a library containing the symbols we need.

所以每个使用 FFI 的函数都需要链接一个动态库。那将是 Data.BufferData.ByteArray。让我们关注第一个,看看会出现什么问题:

所以 Data.Buffer 需要 %dynamic "idris_buffer.so"(而不仅仅是它目前拥有的 %include C "idris_buffer.h")。您可以复制 idris/rts/idris_buffer.(h|c) 并删除需要其他 rts-stuff 的功能。编译共享库 运行:

cc -o idris_buffer.so -fPIC -shared idris_buffer.c

使用修改后的Data.Buffer,你仍然会得到一个错误:

Could not use <<handle {handle: ram.mem}>> as FFI arg.

FFI 调用在 Data.Buffer.readBufferFromFileFile 参数引起了麻烦。那是因为 Idris 看到 openFile 被使用(另一个 C 函数)并且 transforms it in a Haskell call. On the one hand that's nice, because during compilation we're interpreting Idris code, and that make following functions like readLine C/JS/Node/… agnostic. But in this case it's unfortunate, because the Haskell backend doesn't support the returned file handle for FFI calls。所以我们可以编写另一个 fopen : String -> String -> IO Ptr 函数来做同样的事情但有另一个名字所以 Ptr 将保留 Ptr,因为它们可以在 FFI 调用中使用。

完成后,由于内置函数出现另一个错误:

Could not use prim__registerPtr (<<ptr 0x00000000009e2bf0>>) (65546) as FFI arg.

Data.Buffer 在其后端使用 ManagedPtr。是的,这在 FFI 调用中也不支持。所以您需要将它们更改为 Ptr。我猜,这两个 都可以 得到编译器的支持。

最后,一切都应该适用于有效的 %provide (mem : Buffer)。但是,不,因为:

Can't convert pointers back to TT after execution.

尽管 Idris 现在可以在编译时读取文件,但它无法使 Buffer 或任何其他具有 Pnt 的内容可供 运行 访问 - 这是非常合理的.在 运行 时间,只有程序编译时的指针只是随机的事情。因此,您要么需要将数据转换为提供程序中的结果,要么使用像 Provider (List Bits8).

这样的中间格式

I made a short exampleList Bits8 可以在 main 中访问。 Buffer 基本上是 Data.Buffer,包含 _openFilePnt 而不是 ManagedPtr。我希望这能以某种方式帮助你,但也许一些编译器人员可以提供更多背景知识。