拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Bend 外部函数接口(FFI)完全指南:动态链接库的加载、编写与跨后端调用

Bend 外部函数接口FFI完全指南动态链接库的加载、编写与跨后端调用【免费下载链接】BendA massively parallel, high-level programming language项目地址: https://gitcode.com/GitHub_Trending/be/Bend本文以 Bend 的官方文档 docs/ffi.md 为骨架系统讲解如何在运行时通过动态链接库DyLib为 Bend 程序扩展 IO 能力从IO/DyLib/open/IO/DyLib/call/IO/DyLib/close的用法到使用 C/Cuda 语言与 HVM API 编写可被 Bend 调用的外部函数再到-rdynamic等编译链接细节。读完本文你将能够为 Bend 编写、编译并调用自己的 C/CUDA 动态库把任意外部系统能力接入这门大规模并行编程语言。1. 概览为什么需要 FFI 与动态链接库Bend 是一门大规模并行的、高层级编程语言见 README.md。它自带一套围绕文件、网络与标准输入输出设计的 IO 原语定义于 src/fun/builtins.bend但任何语言都无法穷尽所有系统能力。FFIForeign Function Interface的价值在于在程序运行期间加载动态链接库.so/.dylib从而把任意 C/CUDA 实现的函数接入 Bend。Bend 的 FFI 设计有以下特点运行时加载库文件在 Bend 程序运行期才被IO/DyLib/open加载无需在编译期链接这让 Bend 程序可以按需、动态地扩展自身能力与with IO块深度融合加载、调用、关闭都是 IO 操作遵循 Bend 既有的IO单子约定按后端分为两套 API面向 C 运行时的Port fn(Net*, Book*, Port)以及面向 CUDA 运行时的Port fn(GNet*, Port)结果通过Result类型返回与 Bend 内建的 IO 原语保持一致便于错误处理与模式匹配。从源码层面看Bend 的三大 DyLib 原语定义在 src/fun/builtins.bendIO/DyLib/open(path: String, lazy: u24) - IO(Result(u24, String))加载动态库底层通过IO/call(DL_OPEN, (path, lazy))实现IO/DyLib/call(dl: u24, fn: String, args: Any) - IO(Result(Any, String))调用库中函数底层为IO/call(DL_CALL, (dl, (fn, args)))IO/DyLib/close(dl: u24) - IO(Result(None, String))关闭库底层为IO/call(DL_CLOSE, dl)。这三个原语都经由IO/unwrap_inner统一包装返回值是ResultBend 侧通常配合Result/unwrap使用。也就是说Bend 的 DyLib 能力实际上是建立在更底层的IO/call运行时原语之上的理解这一点有助于你排查与调试 FFI 问题。2. 在 Bend 中加载并调用动态库2.1 完整示例目录操作库官方文档 docs/ffi.md 给出了一个处理目录的完整示例。假设我们已经有了一个名为libbend_dirs.so的动态库包含ls与mkdir两个函数Bend 侧的使用方式如下def main(): with IO: # 打开动态库文件 # 第二个参数为 0 表示立即加载所有函数 # 为 1 则表示在使用到函数时才懒加载。 # dl 是动态库的唯一 id。 dl - IO/DyLib/open(./libbend_dirs.so, 0) # 现在可以调用动态库中的函数了。 # 调用者需要知道动态库中提供了哪些函数 # 如果你在为一个依赖动态库的 Bend 库编写封装 # 应当把这些 IO 调用包装起来让使用者无需关心动态库内部细节。 # 第一个参数是动态库 id。 # 第二个参数是要调用的函数名String。 # 第三个参数是传给函数的参数。 # 你需要知道该函数每个参数的类型以及返回值类型。 # 在本例中ls 接收一个路径String # 返回 ls 命令执行结果的字符串。 unwrapped_dl Result/unwrap(dl) files_bytes - IO/DyLib/call(unwrapped_dl, ls, ./) files_str String/decode_utf8(Result/unwrap(files_bytes)) files String/split(files_str, \n) # 我们想在用户 my_user 的目录不存在时创建它。 my_dir List/filter(files, String/equals(my_dir)) match my_dir: case List/Cons: # 目录已存在什么都不做。 * - IO/print(Directory already exists.\n) status wrap(-1) case List/Nil: # 目录不存在创建它。 * - IO/DyLib/call(unwrapped_dl, mkdir, ./my_dir) * - IO/print(Directory created.\n) status wrap(0) status - status # 程序到这里就结束了所以即使不关闭动态库也没有关系 # 但一旦确认不再需要它主动关闭是好习惯。 * - IO/DyLib/close(unwrapped_dl) return wrap(status)2.2 关键用法拆解IO/DyLib/open(path, lazy)的第二个参数lazy是一个编码为u24的布尔值——0表示打开库时立即解析所有函数upfront1表示按需懒加载lazy。前者启动稍慢但调用稳定后者启动快但首次调用某个函数时有额外开销。返回值是Result(u24, String)成功时拿到动态库的唯一整数 id后续所有调用的第一个参数失败时返回错误信息字符串。示例中通过Result/unwrap取出 id。IO/DyLib/call(dl, fn, args)的参数约定dl是库 idfn是函数名字符串args是任意类型的参数。参数与返回值的具体类型由被调函数决定调用者必须事先了解 C 侧的签名约定如字符串会被转换为字节列表。字节与字符串的往返示例中ls返回的是字节列表Bytes因此需要String/decode_utf8(Result/unwrap(files_bytes))解码为字符串再用String/split(files_str, \n)按换行拆分出文件名列表。资源管理示例注释明确说明——程序结束时即使不关闭也无碍但养成主动IO/DyLib/close的习惯能及时释放底层句柄。值得留意的是示例中的status wrap(-1)/wrap(0)Bend 中以负数约定“失败”状态如返回码-1、以非负数约定“成功”状态如返回码0这是一种常见于系统编程的约定你在设计自己的 FFI 返回码时也可以遵循这一模式。3. 编写 Bend 的动态库C 运行时3.1 必备前提Bend IO 库的底层要求Bend 的动态库必须使用C 或 CUDA取决于你面向的后端并基于HVM API实现。HVMHigher-order Virtual Machine是 Bend 的底层运行时因此 FFI 函数的参数、返回值都以 HVM 的Port端口引用为媒介。3.2 函数签名与语义从 Bend 中通过IO/DyLib/call调用的函数必须具有以下签名Port function_name(Net* net, Book* book, Port arg);各参数含义net指向当前网络图状态的指针即程序当前运行时的整体结构book指向函数定义集book of function definitions的指针arg指向该函数参数的Port在本例中即传入的路径字符串。返回值必须是指向函数返回值的Port。HVM 提供了若干工具函数用于 HVM ↔ C 之间的数据转换让你无需深究 HVM 运行时的内部细节即可完成开发readback_str(net, book, arg)把 HVM 侧的字符串参数读回为 C 的Str结构inject_bytes(net, output)把 C 侧的字节缓冲区注入为 HVM 侧的字节列表Bytesnew_port(ERA, 0)构造一个空端口常用来表示“无返回值/失败”。3.3 完整 C 实现ls与mkdir以下代码实现第 2 节示例中使用的库保存为libbend_dirs.c// 包含 HVM API 的头文件。 #include hvm.h // 打开和读取目录所需的头文件。 #include stdio.h #include stdlib.h #include string.h #include errno.h // IO 函数必须使用这个精确签名。 // 第一个参数是指向程序当前状态的图指针。 // 第二个参数是指向函数定义集的指针。 // 第三个参数指向函数的参数。 // 返回值必须是指向函数返回值的端口。 Port ls(Net* net, Book* book, Port arg) { // 参数需要先从 HVM 转换到 C。 // 对 ls 而言参数就是一个字符串。 Str path readback_str(net, book, arg); // 现在可以执行真正的 IO 操作了。 // 这里通过把 ls 作为子进程调用来列出目录内容。 char* cmd malloc(path.len strlen(ls ) 1); sprintf(cmd, ls %s, path.buf); free(path.buf); FILE* pipe popen(cmd, r); if (pipe NULL) { // 最佳实践是返回 Result 类型而不是空值ERA。 // 如果命令失败而调用它的 Bend 程序又试图使用结果 // 结果会被破坏并输出垃圾数据。 fprintf(stderr, failed to run command %s: %s\n, cmd, strerror(errno)); return new_port(ERA, 0); } char buffer[512]; Bytes output { .buf NULL, .len 0 }; while (fgets(buffer, sizeof(buffer), pipe) ! NULL) { size_t len strlen(buffer); char* new_result realloc(output.buf, output.len len 1); if (new_result NULL) { fprintf(stderr, failed to allocate space for output of %s: %s\n, cmd, strerror(errno)); free(cmd); free(output.buf); pclose(pipe); return new_port(ERA, 0); } output.buf new_result; strcpy(output.buf output.len, buffer); output.len len; } // IO 操作完成后把结果转换回 HVM 格式。 // 这里输出的是 ls 命令的输出即字节列表。 // 后续需要在 Bend 中进一步处理把它转换成文件名列表。 Port output_port inject_bytes(net, output); // 记得释放所有分配的内存。 free(cmd); free(output.buf); pclose(pipe); return output_port; } Port mkdir(Net* net, Book* book, Port arg) { // 这里与 ls 函数做的事相同只是调用不产生输出的 mkdir。 Str path readback_str(net, book, arg); char* cmd malloc(path.len strlen(mkdir ) 1); sprintf(cmd, mkdir %s, path.buf); int res system(cmd); free(path.buf); free(cmd); return new_port(ERA, 0); }3.4 关键实现细节解读签名一致性是硬约束注释中反复强调 “IO functions must have this exact signature”任何偏差都会导致 Bend 运行时无法正确调用类型转换是双向的进入时用readback_str把 HVM 数据读成 C 结构返回时用inject_bytes把 C 缓冲区封回 HVM 的Bytes文档同时提示你无需深入 HVM 内部细节即可完成这些转换错误处理建议文档明确建议返回Result类型而非ERA空值——若失败时返回 ERA 而 Bend 端仍尝试解引用结果会得到损坏的数据甚至垃圾输出内存管理是开发者的责任malloc的cmd、path.buf、output.buf都需要在返回前free同时popen的管道要pclose与 C 语言的常规纪律一致mkdir直接返回 ERA因为它没有有意义的返回值Bend 端用* - ...丢弃即可。3.5 编译为共享库假设文件保存为libbend_dirs.c需要使用gcc并以共享库 未解析符号unresolved symbols的方式编译同时包含 HVM 的头文件路径# 需要编译为带有未解析符号的共享库。 # macOS gcc -shared -o libbend_dirs.so -I /path/to/HVM/src/ libbend_dirs.c -undefined dynamic_lookup -fPIC # Linux gcc -shared -o libbend_dirs.so -I /path/to/HVM/src/ libbend_dirs.c -Wl,--unresolved-symbolsignore-all -fPIC要点说明-fPIC生成位置无关代码是共享库的标配-undefined dynamic_lookupmacOS/-Wl,--unresolved-symbolsignore-allLinux允许库中存在来自主程序Bend 生成的 C 可执行文件的符号这正是后面-rdynamic能配合工作的前提-I /path/to/HVM/src/替换为你本机 HVM 源码的实际路径确保能找到hvm.h。编译完成后把库文件路径传给IO/DyLib/open即可在 Bend 中使用。4. 编写面向 CUDA 后端的动态库编写面向 CUDA 运行时的库与 C 运行时非常相似主要区别在于函数签名Port function_name(GNet* gnet, Port argm)其中gnet指向当前网络状态的指针argm函数的参数。返回值同样必须是指向函数返回值的Port。使用nvcc编译器并包含 HVM 头文件来编译。假设文件保存为libbend_dirs.cunvcc -shared -o libbend_dirs.so -I /path/to/hvm/ libbend_dirs.cu与 C 版本相比CUDA 版本签名更精简少了一个book参数这反映了两个后端在运行时架构上的差异CUDA 后端在 GPU 上运行其网络状态由GNet描述。面向不同后端时你的库实现可能需要相应调整。5. 编译使用动态库的 Bend 程序要让动态库能解析来自主程序的符号如 HVM 运行时的readback_str、inject_bytes等编译 Bend 生成的 C/CUDA 程序时必须加上-rdynamic标志把主程序的所有符号导出到动态符号表。假设有一个使用libbend_dirs.so的 Bend 程序my_app.bend编译命令如下# 面向 C 编译 bend gen-c my_app.bend my_app.c gcc -rdynamic -lm my_app.c -o my_app # 面向 CUDA 编译 bend gen-cu my_app.bend my_app.cu nvcc --compiler-options-rdynamic my_app.cu -o my_app从命令行入口源码 src/main.rs 可以看到bend gen-c与bend gen-cu是 Bend CLI 的正式子命令分别“把程序编译为独立的 C / CUDA 文件并输出到 stdout”。由此形成完整的调用链bend gen-c把 Bend 程序编译为 C 代码内部会调用hvm生成器见 src/main.rsgcc使用-rdynamic -lm链接生成可执行文件——-rdynamic导出符号供动态库回引-lm链接数学库运行时IO/DyLib/open加载libbend_dirs.soDL_CALL通过符号名查找并调用其中的ls/mkdir。同时 README.md 也印证了这一工作流Bend 支持使用gen-c和gen-cu把程序编译为独立的 C/CUDA 文件以获得最佳性能并提示代码生成器仍处于早期阶段成熟度不及 GCC、GHC 等编译器——因此在把生产代码完全依赖 FFI 之前建议先在较小范围内验证。6. 常见问题与最佳实践结合文档与源码实现整理出以下实操建议封装优于裸调文档明确建议——如果你在编写一个依赖动态库的 Bend 库应当把IO/DyLib/call包装成语义化的 Bend 函数让库使用者不需要了解动态库内部细节先查函数签名再调用IO/DyLib/call的参数与返回值类型完全由被调函数决定误用类型如把字符串当整数会产生难以排查的错误错误处理优先使用ResultC 侧失败时返回Result而非 ERA避免 Bend 侧解引用损坏数据Bend 侧统一用Result/unwrap解包及时关闭动态库虽然进程结束时会自动清理但主动IO/DyLib/close更规范平台差异不可忽视C 库编译时 macOS 用-undefined dynamic_lookupLinux 用-Wl,--unresolved-symbolsignore-all链接主程序时-rdynamic两个平台通用区分后端 API面向 C 运行时签名是Port fn(Net*, Book*, Port)面向 CUDA 是Port fn(GNet*, Port)不要混用。7. 延伸阅读docs/ffi.md官方 FFI 文档原文包含全部示例代码src/fun/builtins.bendIO/DyLib/open/call/close的原语定义及其参数语义src/fun/builtins.bendIO/unwrap_inner的实现理解Result包装层如何工作src/main.rsgen-c/gen-cu命令行子命令的定义README.mdBend 编译为独立 C/CUDA 文件的说明与代码生成器成熟度提示。【免费下载链接】BendA massively parallel, high-level programming language项目地址: https://gitcode.com/GitHub_Trending/be/Bend创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门