欢迎加入开源鸿蒙PC社区https://harmonypc.csdn.net/欢迎在PC社区平台申请新建项目https://atomgit.com/OpenHarmonyPCDeveloperISPC 的可执行文件编出来之后第一次运行--version就抛出了std::bad_cast。继续排查又遇到了“构建成功但生成的位码没有所需符号”的情况。对编译器做移植编译结束只是其中一步它还得能把用户的代码编译出来并让生成的程序在目标设备上算出正确结果。这篇文章记录 ISPC 1.28.2 在鸿蒙PC aarch64 上的适配过程重点讨论依赖、C 运行库、构建脚本和实际执行之间的关系。配方位于 AtomGit 社区仓库OpenHarmonyPCDeveloper/build_in_harmonyos的archives/i/ispc/1.28.2对应 PR !15301。任务记录显示该 PR 于 2026 年 10 月 18 日合并最终 CI 为 build #98211。本文使用设备原始日志说明上游测试结果并重新执行了一次消费者示例。上游用例通过率为 2113/222395.05%仍有 3 个失败和 107 个按任务口径归入跳过的用例具体分类在后文列出。文章目录1. 先明确要移植的是什么2. 为什么先做了一个 LLVM 依赖包3. 一个版本判断为什么会挡住编译4. 编译通过为什么连版本号都打印不出来5. 最容易漏掉的失败空位码也能让构建继续6. 同样叫 clang调用到的可能不是同一个7. 在鸿蒙PC上编译并运行一个数值程序7.1 编写 ISPC 内核7.2 编写 C 校验程序7.3 设置包路径与运行环境7.4 编译、链接并执行7.5 核对输出与真机截图9. CI 红灯要按失败位置处理10. 这次适配留下了什么资料与延伸阅读1. 先明确要移植的是什么ISPC 全称是 Intel Implicit SPMD Program Compiler。它接受带有 SPMD 扩展的 C 风格代码通过 LLVM 生成目标代码。以数组运算为例可以用foreach描述各元素的计算再由编译器面向相应的 SIMD 目标生成实现。本次交付的是命令行编译器bin/ispc不包含 libispc/JIT 库。验证重点是 aarch64/NEON 原生编译和执行保留 X86 后端用于代码生成与相关测试并不表示鸿蒙PC可以直接运行 x86 机器码。文中的数值示例只验证正确性没有测量加速比。本次适配记录中的环境如下项目版本或配置设备系统、架构HarmonyOS、aarch64设备资源8 核、32 GB 内存ISPC1.28.2构建 ISPC 的设备编译器clang/clang 15.0.4ISPC 使用的 LLVM 依赖llvm-project/20.1.8构建工具Conan 2.29.1、CMake 4.1.2、Ninja 1.13.1这里同时出现了 LLVM 20 和 clang 15。前者是 ISPC 所依赖的编译基础设施后者是设备上用来构建、链接程序的工具链。后面两次故障都与这两套工具的边界有关。2. 为什么先做了一个 LLVM 依赖包ISPC 需要的不只是一个llvm-config。构建还会使用 Clang 前端组件库、LLVM 目标后端以及clang、clang、llvm-as、FileCheck等工具。适配前的依赖检查发现当时可用的几个包各有缺口llvm/20.1.0缺少所需的 Clang 前端库后端也不完整clang/20.1.8的远端二进制下载返回 404clang/22.1.8则超出了 ISPC 1.28.2 的 LLVM 支持范围检查时也遇到了 API 不兼容。因此先独立适配了llvm-project/20.1.8对应依赖 PR !15277。这个包提供 LLVM 和 Clang 的 CMake 配置、所需的前端静态库、libLLVM.so.20.1以及配套工具后端包含 AArch64、ARM 和 X86。其中 ARM 与 AArch64 不能想当然地互相替代该版本 ISPC 的配置检查会同时检查这两个后端。X86 后端也有用途上游测试中存在依赖X86_ENABLED的代码生成用例。只按宿主架构裁掉其他后端会改变能够验证的范围。这一步决定了后面的构建方案。遇到编译器项目时先列清楚它需要哪些库、哪些可执行工具、哪些目标后端比看到一个同名依赖就开始构建更稳妥。3. 一个版本判断为什么会挡住编译第一处源码编译错误出现在src/util.cpperror: exception specification in declaration does not match previous declarationISPC 在这里定义了std::__libcpp_verbose_abort并根据所链接 LLVM 的版本决定是否添加noexcept。但当前环境中LLVM 是 20.1.8宿主 SDK 的 libc 来自 15.0.4两者的函数声明并不相同。这类错误应当与当前参与编译的头文件对齐。最终补丁把判断条件改为宿主 libc 的版本-#if ISPC_LLVM_VERSION ISPC_LLVM_20_0 #if _LIBCPP_VERSION 200000 noexcept #endif这是本次适配唯一的上游源码补丁其余处理放在配方、构建参数或包装脚本中。修改范围小的原因也很具体这里的问题是异常说明与头文件声明不匹配没有必要改动 ISPC 的代码生成逻辑。4. 编译通过为什么连版本号都打印不出来解决声明问题之后可执行文件已经生成启动时却报错libcabi: terminating due to uncaught exception of type std::bad_cast排查发现打包选项会启用静态链接设置向链接命令加入-static-libstdc。在这套 OHOS clang 工具链中该选项作用于 libc使可执行文件静态带入一份 C 标准库。与此同时依赖的libLLVM.so.20.1又使用动态的libc_shared.so。任务中的最小复现对比了三种组合链接组合设备上的结果动态 C 标准库加 LLVM 动态库正常运行静态 C 标准库加 LLVM 动态库启动异常出现std::bad_cast静态 C 标准库不链接 LLVM正常运行这组对照把问题缩小到了当前依赖组合中的运行库混用。最终显式关闭ISPC_STATIC_LINK和ISPC_STATIC_STDCXX_LINK让 ISPC 与 LLVM 使用一致的动态 C 运行库。这不是说静态链接普遍不可用。这里需要避免的是同一个进程中的这组混用方式。若程序在进入实际业务前就因 iostream、locale 等标准库操作崩溃检查可执行文件和依赖库的动态依赖比直接修改业务代码更有针对性。5. 最容易漏掉的失败空位码也能让构建继续另一个问题直到功能测试阶段才显现。21 个涉及 print/asin 的用例在链接时报ld.lld: error: undefined symbol: __do_print继续向前追发现用于生成 builtins 的编译命令引用了 Debian multiarch 的系统头目录。鸿蒙设备上没有对应目录前面的clang已经报出找不到unistd.h。但这条自定义命令使用了管道clang ... -o - | llvm-as -o out.bc前一个程序失败后面的llvm-as仍然接收空输入并生成空模块。由于这条命令的退出状态取自管道末尾构建没有在真正出错的位置停下。修复前四个builtins_cpp_*.bc文件都是 1304 字节符号检查找不到所需定义。配方随后通过包装脚本取得设备 clang 的 C 头文件搜索目录并按系统头目录注入 builtins 的编译过程。构建后还增加了文件大小检查这些位码文件若小于 8 KiB就立即报错。这是针对已观察到的空模块故障设置的检查不能单独证明位码正确符号检查和后续功能测试仍然需要保留。这个问题给出的教训很直接生成文件存在不代表生成步骤成功。对管道里的工具要检查前端诊断也要检查产物是否包含预期内容。6. 同样叫 clang调用到的可能不是同一个后续测试又报了找不到stdio.h、iostream或cmath。这些错误看起来像缺少 SDK 头文件但实际原因是编译器路径发生了变化。LLVM 依赖包也带有clang。测试环境把依赖包的bin放到 PATH 前面后裸命令clang会解析到依赖包中的程序而不是设备上配置好目标系统环境的编译器。lit 测试与 Conan 消费者测试都遇到了这个问题只是发生的位置不同。lit 侧使用排在前面的包装脚本指向设备编译器消费者测试则只接受可用的绝对路径依次检查 Conan 配置、绝对路径形式的CXX最后使用设备工具链路径。本文示例的 C 链接步骤因此明确写为/data/service/hnp/bin/clang。这条路径是本机的实际配置换设备时应先核对不能只凭命令名称判断工具来源。CI 还暴露出一处构建阶段的网络访问ISPC_PREPARE_PACKAGEON会注册ispc_web目标从外网克隆文档站仓库。最终关闭该选项并从已有源码补齐许可、说明、示例等静态文件。核对新旧安装清单后差异为少了五个网页文件其余 132 项一致。这样处理的是文档获取方式没有据此裁掉编译器的验证步骤。7. 在鸿蒙PC上编译并运行一个数值程序下面使用适配目录中现有的消费者测试。ISPC 内核完成数组相加和点积C 驱动负责准备数据、调用生成的函数并检查结果。7.1 编写 ISPC 内核将以下内容保存为test.ispcexportvoidadd_arrays(uniformfloata[],uniformfloatb[],uniformfloatc[],uniformintn){foreach(i0...n){c[i]a[i]b[i];}}export uniformfloatdot_product(uniformfloata[],uniformfloatb[],uniformintn){floatacc0;foreach(i0...n){acca[i]*b[i];}returnreduce_add(acc);}foreach表达各元素上的计算reduce_add汇总各程序实例的部分结果。7.2 编写 C 校验程序保存 C 驱动为test.cpp#includecmath#includecstdio#includeispc_probe.hintmain(){constintn64;floata[n],b[n],c[n];for(inti0;in;i){a[i]static_castfloat(i)*0.5f;b[i]static_castfloat(i)1.0f;c[i]-1.0f;}ispc::add_arrays(a,b,c,n);for(inti0;in;i){if(c[i]!a[i]b[i]){std::printf(FAIL add_arrays at %d: %f ! %f\n,i,c[i],a[i]b[i]);return1;}}constfloatdotispc::dot_product(a,b,n);floatexpect0.0f;for(inti0;in;i)expecta[i]*b[i];if(std::fabs(dot-expect)1e-3f*std::fabs(expect)){std::printf(FAIL dot_product: %f ! %f\n,dot,expect);return1;}std::printf(ispc probe ok: add_arrays(%d) dot_product %f\n,n,dot);return0;}生成的头文件把接口放在ispc命名空间中因此 C 侧使用ispc::add_arrays和ispc::dot_product。这个示例使用固定的有限数值输入检查数组结果与点积它不是覆盖 NaN、无穷大等情况的通用浮点测试。7.3 设置包路径与运行环境执行前需准备好已构建的 ISPC 包、LLVM 依赖包和设备 C 工具链。下面两条缓存路径来自本次设备复跑其他环境必须替换为对应包的实际路径。Conan 缓存目录不具有跨设备固定性。exportISPC/storage/Users/currentUser/.conan2/p/b/ispc95f90df04bb69/p/bin/ispcexportLLVM_LIB/storage/Users/currentUser/.conan2/p/b/llvm-a4635c5fa43c9/p/libexportLD_LIBRARY_PATH$LLVM_LIB${LD_LIBRARY_PATH::$LD_LIBRARY_PATH}7.4 编译、链接并执行在存放两个源码文件的可写目录执行(set-euuname-suname-m$ISPC--version$ISPCtest.ispc--archaarch64--targetneon-i32x4--pic\-otest.o-hispc_probe.h /data/service/hnp/bin/clang test.cpp test.o -I.-oispc_probe-lm./ispc_probe)7.5 核对输出与真机截图2026 年 10 月 20 日在设备的独立临时目录中重新编译和执行得到HarmonyOS aarch64 Intel(r) Implicit SPMD Program Compiler (Intel(r) ISPC), 1.28.2 (build commit 20261014, LLVM 20.1.8) ispc probe ok: add_arrays(64) dot_product 43680.000000这次验证实际经过了 ISPC 代码生成、C 链接和设备执行。如果加载 ISPC 时提示缺少libLLVM.so.20.1应先核对 LLVM 包的lib路径只添加 ISPC 自己的库目录并不充分。2223 个上游用例结果怎么读完整的上游测试来自两套入口lit 的check-all和scripts/run_tests.py驱动的 func-tests。设备原始日志中的分类如下套件总数PASSFAILUnsupportedXFAILSKIPPEDlit64754639440func-tests157615670009合计2223211339449任务汇总把 94 个 Unsupported、4 个 XFAIL 和 9 个 SKIPPED 合并记为 107 个 SKIP。这里展开原始分类是因为 XFAIL 表示预期失败含义与未执行的跳过不同。按全部 2223 个用例为分母PASS 比例为 2113/222395.05%。三个 FAIL 分别是math-lib-value.ispc、2780-1.ispc和3571.ispc。任务分析将其归因于 x86 宿主相关假设分别涉及在 aarch64 宿主目标上使用 SVML、用宿主编译器处理 x86 专用头以及把 x86 对象文件交给 aarch64 链接器。这些失败保留在统计里没有通过删除用例来提高比例。前面的消费者程序另算一个鸿蒙验证用例不加入上游 2223 的分母。64 次逐元素检查也不能写成新增了 64 个上游测试。CI 的汇总数字曾与设备全量日志不同。素材记录了测试驱动回收队列结果时可能存在的竞态但仅凭汇总退出码无法逐项补齐缺失回执。因此本文采用已有完整设备日志的计数不把两轮结果拼成一张“全部通过”的表。9. CI 红灯要按失败位置处理任务记录中共有 13 次 CI 结论前 12 次失败最后一次通过。失败既包含已经修复的配方问题也包含环境准备、进程创建、下载工具和限流相关问题还有一轮分类未明确不能把它们统称为基础设施故障。例如构建期间克隆文档仓库失败需要消除隐式网络依赖消费者找不到cmath需要修正编译器选择。若日志在环境准备阶段就出现无法创建进程则应继续检查对应 worker 的资源与环境状态不能据此修改 ISPC 算法。也不能只看cant fork就断言物理内存耗尽。它说明创建进程失败进一步归因仍需要当时的资源和限制证据。把失败阶段、首个有效错误和本轮修改分开记录才能判断下一步该修代码、修配置还是等待运行环境恢复。10. 这次适配留下了什么ISPC 1.28.2 已在这台鸿蒙PC上完成实际代码生成、链接和数值执行。适配中可复用的处理集中在几个具体位置依赖包要覆盖所需的前端库与后端异常说明要跟随宿主头文件主程序与动态依赖的 C 运行库要保持一致生成步骤要检查真实产物测试使用的编译器要能确定来源。这些检查能帮助定位“本地能编、CI 不能编”或“编译成功、启动就崩”的问题。当前结果也有清楚的边界上游测试保留了失败与跳过项没有验证 GPU/JIT 能力也没有性能加速结论。任务清单中的制品状态仍为built不能仅凭 PR 合并和 CI 通过就写成已发布到制品仓。适配源码可在 AtomGit 社区仓库 中按archives/i/ispc/1.28.2查找最终 CI 记录为 build #98211。资料与延伸阅读ISPC 上游项目MLton 鸿蒙PC适配文章Actiona 鸿蒙PC适配文章SDCC 鸿蒙PC适配文章后三篇用于参考技术复盘的组织方式本文的 ISPC 问题、代码与测试数据来自本次适配材料和设备验证。