深入 Roc test/int 测试平台:宿主多参数传递、Box 不透明类型与 ABI 布局
深入 Roc test/int 测试平台宿主多参数传递、Box 不透明类型与 ABI 布局【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roctest/int是 Roc 编译器仓库中一个用于验证宿主Host与 Roc 之间参数传递的基础测试平台它以 Zig 编写的 host 程序为驱动把外部数据传入 Roc 代码并取回结果覆盖了Box(model)不透明类型跨边界、纯函数与 effectful 函数、混合对齐参数与结构体字段布局等多类 ABI 场景。读完本文你将掌握该测试平台的构建与运行方式理解platform声明、provides导出符号与targets链接配置的协作关系并能够据此搭建自己的 Roc Zig host 传参测试环境。测试平台概览它测什么根据 test/int/README.md 的说明这个目录是一个primitive test platform基础测试平台核心用途是演示如何从 host 向 Roc 传递多个参数。README 给出的函数签名为I64, I64 - I64描述为从 host 取两个随机整数并返回它们的乘积。需要说明的是这是平台用途的概括性描述从当前仓库源码看该平台实际承载的测试面更广这也是它被归入测试平台而非普通示例的原因。综合 test/int/app.roc 与 test/int/platform/host.zig该平台真正验证的能力包括Box 不透明类型跨边界Box(Model)在 host 侧是不可见的指针宽度值host 只负责创建、传递、消费不感知内部结构init / update / render 模式平台要求应用导出的main符合init : {} - model、update : model, I64 - model、render : model - Simple(model)的形状并逐一在 host 中调用多参数与混合对齐参数传递从 host 传入(Bool, I64)等对齐方式不同的参数验证 Roc ABI 对参数的排序与布局规则对应 issue 8991 的回归测试结构体参数往返包含U64、F32、Bool等混合字段的 record以及纯函数 / effectful两种入口形态的对比验证。整个平台由三个文件 一组链接目标构成test/int/ ├── README.md # 使用说明 ├── app.roc # 被测 Roc 应用init/update/render └── platform/ ├── main.roc # 平台声明 导出给 host 的函数 ├── Simple.roc # Simple 能力opaque type ├── host.zig # Zig host驱动 11 项边界测试 └── targets/ # 各目标平台的链接输入crt、libc 等构建与运行两条命令跑通测试README 给出了完整的两步操作直接照做即可zig build # Run (ignore cached files) ./zig-out/bin/roc --no-cache test/int/app.roc第一步zig build会通过仓库根目录的 build.zig 完成两件事编译出 Roc 编译器本体zig-out/bin/roc以及把str、int两个测试平台的 host 库构建并复制到对应targets/目录中。相关逻辑位于 build.zig 的main_build_platforms配置// Build str and int test platform host libraries for native target // (fx and fx-open are only built by build-test-hosts for CLI platform tests) const main_build_platforms [_][]const u8{ str, int };这段代码位于 build.zig会针对原生目标以及所有 Linux 交叉目标musl glibc逐一调用buildAndCopyTestPlatformHostLib把编译好的 host 库放到test/int/platform/targets/target/下如x64musl/libhost.a、x64glibc/libhost.a等。同时all_test_platform_dirs常量build.zig把int与str、fx、fx-open、dylib、archive等一并登记为需要构建 host 库的测试平台表明这是 Roc 的 CLI 集成测试体系中的一环。第二步中--no-cache是 Roc 编译器的选项含义是忽略缓存文件、强制重新编译。由于平台源码或targets/中的 host 库可能刚被重新构建加上--no-cache可以避免读到陈旧的缓存产物确保本次运行使用的是最新代码。被测应用侧app.roc 的 init/update/render 契约test/int/app.roc 定义了被测试的 Roc 应用全文仅 11 行却完整示范了应用如何接入一个自定义平台app [Model, main] { pf: platform ./platform/main.roc } import pf.Simple Model : { value: I64 } main { init: |{}| { value: 0 }, update: |m, delta| { value: m.value delta }, render: |_m| Simple.leaf(hello), }逐行解读第 1 行app [Model, main] { pf: platform ./platform/main.roc }声明这是一个 Roc 应用导出Model与main两个符号并通过pf这个名字接入./platform/main.roc定义的平台。路径相对于 app.roc 所在目录即本仓库的 test/int/platform/main.roc。第 3 行import pf.Simple从平台导入Simple能力opaque type供render使用。第 5 行Model : { value: I64 }应用的模型是一个仅含I64字段的 record。第 7-11 行main应用主入口必须按平台requires的契约提供init、update、render三个函数——init产生初始模型{ value: 0 }update把模型与一个I64增量相加render忽略模型内容返回Simple.leaf(hello)。注意render返回的Simple(Model)是平台通过 test/int/platform/Simple.roc 暴露的不透明类型Simple(model) : [Leaf(Str)].{ leaf : Str - Simple(model) leaf |s| Leaf(s) }它定义一个带 payload 的标签联合[Leaf(Str)]并附带构造函数leaf。对 host 而言这是一个看得见大小、看不见结构的盒子host 只需按extern struct { opaque_bytes: [8]u64 }那样预留足够的字节见下文 host.zig而无需关心其内部布局。平台侧main.roc 的声明、导出与链接目标test/int/platform/main.roc 是平台的主控文件第一段是完整的platform声明platform requires { [Model : model] for main : { init : {} - model, update : model, I64 - model, render : model - Simple(model) } } exposes [Simple] packages {} provides { roc_init: init_for_host, roc_update: update_for_host, roc_render: render_for_host, roc_test_mixed_args: test_mixed_args_for_host, roc_test_struct_arg: test_struct_arg_for_host, roc_test_effectful_struct_arg: test_effectful_struct_arg_for_host!, roc_test_simple_pure: test_simple_pure_for_host, roc_test_simple_effectful: test_simple_effectful_for_host!, roc_test_three_floats_pure: test_three_floats_pure_for_host, roc_test_three_floats_effectful: test_three_floats_effectful_for_host! } targets: { inputs_dir: targets/, x64mac: { inputs: [libhost.a, app] }, arm64mac: { inputs: [libhost.a, app] }, x64musl: { inputs: [crt1.o, libhost.a, app, libc.a] }, x64v1musl: { inputs: [crt1.o, libhost.a, app, libc.a] }, arm64musl: { inputs: [crt1.o, libhost.a, app, libc.a] }, arm64v1musl: { inputs: [crt1.o, libhost.a, app, libc.a] }, x64glibc: { inputs: [Scrt1.o, crti.o, libhost.a, app, crtn.o, libc.so] }, arm64glibc: { inputs: [Scrt1.o, crti.o, libhost.a, app, crtn.o, libc.so] }, x64win: { inputs: [host.lib, app] }, arm64win: { inputs: [host.lib, app] }, x64mingw: { inputs: [crt2.obj, host.lib, app, libmingw32.lib, zigc.lib, compiler_rt.lib, api-ms-win-crt-*.lib, advapi32.lib, kernel32.lib, ntdll.lib, shell32.lib, user32.lib] }, arm64mingw: { inputs: [crt2.obj, host.lib, app, libmingw32.lib, zigc.lib, compiler_rt.lib, api-ms-win-crt-*.lib, advapi32.lib, kernel32.lib, ntdll.lib, shell32.lib, user32.lib] }, }各段含义如下requires定义应用必须满足的接口。[Model : model] for main引入类型变量model并把它与应用的main绑定——这里特别把Model提升为类型别名源码注释指出它是for子句引入的、在类型检查时与应用的具体类型统一init/update/render的签名即上文 app.roc 实现的契约。exposes [Simple]把Simple能力开放给应用侧使用。packages {}平台自身的依赖包此处为空。provides平台导出给 host 的符号表。每个条目形如C 符号名: Roc 函数包括roc_init、roc_update、roc_render三个主入口以及 8 个面向 ABI 测试的导出。注意带!的符号如roc_test_effectful_struct_arg、roc_test_simple_effectful、roc_test_three_floats_effectful是effectful 函数能力为与纯函数-形成对照实验组。targets声明链接时需要拼进最终可执行文件的输入文件按目标平台分列。inputs_dir: targets/指向 test/int/platform/targets/其中的crt1.o、Scrt1.o、crti.o、crtn.o、libc.a等即仓库内为各平台检入的 C 运行时与 libc 文件libhost.a/host.lib则来自zig build时编译出的 host 库。app是占位符链接时会被替换为编译后的 Roc 应用目标文件。平台导出函数的实现细节provides中声明的每个函数都有显式类型标注与实现test/int/platform/main.roc# Explicit type annotations for host-facing functions init_for_host : {} - Box(Model) init_for_host |{}| { init_fn main.init record init_fn({}) Box.box(record) } update_for_host : Box(Model), I64 - Box(Model) update_for_host |boxed_model, value| { m Box.unbox(boxed_model) update_fn main.update Box.box(update_fn(m, value)) } render_for_host : Box(Model) - Simple(Model) render_for_host |boxed_model| { m Box.unbox(boxed_model) render_fn main.render render_fn(m) }三个主入口统一采用Box封装模型init_for_host调用main.init({})得到 record用Box.box装箱后返回——host 拿到的是一个不透明指针无法解包update_for_host用Box.unbox取出模型、应用main.update后再装箱。注意unbox会消费传入的 Box因此 host 侧不能复用同一个 Box 值host.zig 中对此有明确注释render_for_host解箱后调用main.render返回Simple(Model)——对 host 而言是带 discriminant 的标签联合。随后的 8 个导出函数则是 ABI 专项测试详见下一节它们在 Roc 侧只做原样读回字段的工作把布局验证的压力全部交给 host 侧比对。Host 侧host.zig 如何驱动 11 项测试test/int/platform/host.zig 是真正的考官。它通过extern fn声明直接调用 Roc 导出符号使用 C ABI并实现了完整的运行时支撑。运行时基础设施HostEnv持有 arena 分配器rocAllocator把它提供给 Roc 运行时host.zigRocOpsg_roc_ops组装了roc_alloc、roc_dealloc、roc_realloc、roc_dbg、roc_expect_failed、roc_crashed等回调host.zig并在comptime块中通过host_alloc.exportRuntimeSymbols导出host.zig——这是 Roc 代码能够在 host 进程中分配/释放内存的前提入口处理main以 C ABI 导出调用platform_main()host.zig针对 Windows MinGW/MSVCRT 还额外导出了__main兼容桩host.zigBox usizehost 侧把 Box 定义为指针宽度的不透明值host.zig只能原样传递。边界类型布局extern struct 的字段顺序host 侧用extern struct精确复刻 Roc record 的内存布局字段顺序与 Roc 源码中的书写顺序无关而是按对齐降序、字段名字母序排列Roc record 布局约定。以FrameInput为例host.zigconst FrameInput extern struct { frame_count: u64, // 8 字节对齐offset 0 mouse_wheel: f32, // 4 字节对齐offset 8 mouse_x: f32, // offset 12 mouse_y: f32, // offset 16 mouse_left: bool, // 1 字节对齐offset 20 mouse_middle: bool, // offset 21 mouse_right: bool, // offset 22 // 1 字节 padding总大小 24 字节对齐到 8 };对照 test/int/platform/main.roc 中 Roc 侧的FrameInput声明书写顺序是frame_count / mouse_x / mouse_y / mouse_left / mouse_middle / mouse_right / mouse_wheel可见 host 侧必须按frame_count / mouse_wheel / mouse_x / mouse_y / mouse_left / mouse_middle / mouse_right的布局规则重排。这正是本平台要验证的核心 ABI 规则之一。11 项测试的执行逻辑platform_main()host.zig依次执行 11 项测试并计数每项通过打印绿色SUCCESS#测试验证点1roc_init()init返回Box(Model)host 能看到不透明指针2roc_update(boxed_model, 42)update消费一个 Box、返回新 Boxhost 侧不能再复用旧 Box3roc_render(updated_model)render返回Simple(Model)标签联合不崩溃4init render新 Box可反复创建并消费多个 Box5roc_test_mixed_args(Bool, I64)混合对齐参数issue 8991host 传入MixedArgs{ value: i64, flag: bool }按对齐排序I64 在前Roc 返回(Bool, I64)元组并逐字段比对6roc_test_struct_arg(FrameInput)含 U64/F32/Bool 的 7 字段结构体往返逐字段比对7roc_test_effectful_struct_arg(FrameInput)effectful版本的同一测试——issue 8991 曾表现为 effectful 入口布局错位而纯函数正常8roc_test_simple_pure(SimpleInput)2 字段U64 Bool纯函数隔离变量9roc_test_simple_effectful(SimpleInput)2 字段 effectful 版本10roc_test_three_floats_pure(ThreeFloats)3 个同对齐 F32 字段验证字母序排序aaa/bbb/ccc11roc_test_three_floats_effectful(ThreeFloats)3 字段 effectful 版本测试 5 特别展示了Roc 函数签名顺序 ≠ 内存布局顺序这一关键事实虽然导出函数是test_mixed_args_for_host : Bool, I64 - (Bool, I64)但按 Roc ABI参数按对齐降序排列因此 host 侧构造的参数结构体是MixedArgs{ value: i64, flag: bool }8 字节对齐的i64在前1 字节对齐的bool在后并以mixed_args.flag, mixed_args.value的顺序传参host.zig。结果通过(Bool, I64)元组返回后再与输入逐项比对。测试 7、9、11 分别对应纯函数 / effectful 的对照若 effectful 入口存在布局错位host 会打印FAILED ... This is issue 8991.全部 11 项通过时输出ALL TESTS PASSED: Box(model) and mixed args work correctly across host boundary!否则以退出码 1 结束host.zig。这种纯函数与 effectful 成对出现的设计使该平台既能回归修复前的 bug又能防止修复后再退化。延伸从测试平台到平台开发方法test/int的价值不止于自身测试它示范了 Roc 平台开发的三条通用经验三步闭环app.roc应用契约→platform/main.rocrequires/exposes/provides/targets声明 导出实现→host.zigC ABI 调用 RocOps运行时支撑三者构成一个可独立构建、可执行、可断言的闭环Box 隔离当 host 不需要理解应用模型时Box(Model)是简洁且安全的边界——host 只搬运指针unbox的消费语义需要在 host 注释中明确记录本平台用每个 Box 只消费一次的测试序列固化这一约定ABI 以布局为准无论是参数还是 record 字段Roc 侧源码书写顺序都不代表内存顺序以对齐降序、字段名字母序为准则host 侧应像extern struct那样显式声明布局并逐一比对字段任何错位都会在测试 5-11 中暴露。如果你要搭建自己的 Roc Zig host 测试平台可直接以本目录为模板拷贝app.roc与platform/结构修改requires中的模型类型与provides中的导出符号并在host.zig的platform_main中按构造输入 → 调用 extern fn → 比对返回值的模式追加断言即可。需要同时验证多目标时参照targets:段为每个平台列出对应的 crt / libc 输入并由 build.zig 的buildAndCopyTestPlatformHostLib逻辑自动完成 host 库的构建与分发。【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考