首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
LLVM 项目完全拆解:从 IR 架构到 llvmpipe 与 SIMD 实践
📅 2026/9/19 14:53:09
✍️ 爱科研究院
👁 阅读 3,247
我这两年跟 LLVM 打交道的时间比跟家里人都熟。年初为了给一个自研的脚本语言做 JIT 编译后端把 llvm-project 从 GitHub 拉下来从头编了一遍中间踩坑无数但也算把这套庞然大物的骨架摸了个七七八八。最近看到不少朋友在讨论 llvmpipeMesa 里那个基于 LLVM 的软件渲染器还带 llvm 15.0.7 版本标识和 256 bits 向量宽度正好借着这个由头把整个 LLVM 项目从架构设计到实际操作写一篇尽量完整的拆解。如果你是刚接触编译原理、想用 LLVM 做点东西或者只是好奇这玩意为什么能撑起整个现代编译器生态这篇文章应该能给你一个相对清晰的全局视图。1. 先搞清楚 LLVM 到底是什么不只是“一个编译器”我第一次接触 LLVM 的时候也被它的名字误导了很久。Low Level Virtual Machine听起来像是个虚拟机但实际它和你理解的 JVM、V8 完全是两码事。它不是一个“跑字节码的运行时环境”而是一整套用来构建编译器的“乐高积木”。你可以用它的库和基础设施拼出自己的语言前端、优化器、后端甚至是一个像 llvmpipe 这样把 GPU 着色器翻译成 CPU SIMD 指令的软件渲染器。真正让 LLVM 成为今天这个生态霸主的原因是它把一个编译器按“前端-中间层-后端”切成了三段并且把中间层IR做成了统一、稳定、可读性极高的“通用语言”。GCC 也做类似的事但 LLVM 的模块化程度更高、代码库更干净、API 更现代化这让它成了学术界和工业界共同的选择。1.1 三层架构里的“通用语”LLVM IR 为什么是灵魂所在要理解 LLVM必须先理解 IR。IR 是 LLVM 的一种中间表示它长得很像简化的 C 语言或者说是“经过类型标注的三地址码”。比如a b * c这样一句 C 代码在经过 clang 翻译成 LLVM IR 后会变成类似这样的形式%1 load i32, i32* %b %2 load i32, i32* %c %3 mul nsw i32 %1, %2 %4 load i32, i32* %a %5 add nsw i32 %4, %3 store i32 %5, i32* %a每一个操作都被拆成了一条独立的指令每条指令都显式地标注了数据类型i32 表示 32 位整数、操作数来源和结果去向。这种设计带来的最大好处是任何前端只要能把源代码翻译成 IR优化器和后端就都可以直接复用。你写一门新语言不需要重新发明一套优化框架也不用管 x86、ARM、RISC-V 之间的寄存器差异只要把 IR 丢给 LLVM 的优化 pass它就能自动帮你完成死代码消除、常量折叠、循环展开等几十轮优化最后再交给后端生成对应平台的机器码。这个设计出来的时间其实非常早——2000 年左右 Chris Lattner 在 UIUC 做博士论文时就是这个思路之后苹果把它的作者和团队一起收了进去。现在 LLVM 已经迭代到了 15.x、16.x、17.x 的版本但核心架构一点没变只是 IR 的特性越来越丰富优化 pass 越来越多后端架构越来越完整。1.2 不只是编译器LLVM 项目的家族成员很多人以为 LLVM 就是“一个编译器”实际 llvm-project 这个仓库里装的是一个完整的生态。我现在用的 15.0.7 版本的仓库里主要包含这些子项目llvm核心编译器基础设施就是 IR 定义、优化 pass、代码生成、汇编器、链接器等底层组件。这是整个项目的心脏。clangC/C/Objective-C 的前端。这是所有人接触 LLVM 的第一道门把 C/C 源码翻译成 IR。lld一个高性能的链接器目标是把链接速度做到极致。比系统自带的 GNU ld 快好几倍Android 和 Chrome 团队都在用。libc / libcabiC 标准库的 LLVM 实现是 clang 的配套标准库。compiler-rt提供一些底层运行时支持比如地址消毒器AddressSanitizer、未定义行为消毒器UBSan等工具的运行时库。mlirMLIR 项目一个用于构建编译器的编译器框架可以理解为“IR 的 IR”。它吸收了 LLVM IR 的设计理念但更灵活专门用来做编译器工具链的硬件/软件协同设计、AI 模型编译如 TensorFlow、PyTorch 的一些编译器后端就是基于 MLIR等。polly一个基于多面体模型的循环优化器可以对循环进行非常激进的重组和并行化主要给高性能计算场景用。flang、openmp、pstlFortran 前端、OpenMP 实现、并行 STL 实现等较专业的分支。llvmpipe虽然 llvmpipe 的源码实际托管在 Mesa 项目里但它属于 LLVM 生态的一部分。它利用 LLVM 的 JITJust-In-Time编译能力把 OpenGL/Vulkan 的着色器代码在运行时编译成 CPU 的 SIMD 指令用纯软件的方式执行渲染管线。为什么要这么干因为这能解决一个很实际的问题当你在一台没有合适 GPU 驱动的机器上跑图形程序时llvmpipe 能给你一个兜底的软件渲染方案让程序不至于直接崩溃。这就要靠 LLVM 的模块化和 JIT 能力撑起来。llvm-project 现在有几千个活跃贡献者横跨苹果、谷歌、ARM、索尼、高通等一堆巨头。这个生态的影响力已经远远超出“编译器”的范畴任何需要做代码生成、静态分析、语言开发的场景几乎都能从中找到现有方案。2. 自己动手构建 LLVM从源码拉取到可用的 clang如果你只是想用 clang 编译 C 语言那没必要从源码构建直接去官网下载预编译包就行——Linux 发行版也有现成的。但如果你想做开发比如想改 clang 的行为、想给 LLVM 新增一个目标后端、想写一个自定义的优化 pass或者想调试验证某个版本的 bug那你就需要从源码构建一套完整的工具链了。2.1 准备阶段的几个关键取舍版本、磁盘、内存首先说版本我踩过最大的一个坑是版本匹配问题。llvm-project 是一个大仓库相对于 git tag 你会看到 release/15.x 这样的分支。如果你同时还要用 Mesa 的 llvmpipe那要注意 Mesa 对 LLVM 版本有要求选 15.0.7 是因为 Mesa 某个版本要求最低 LLVM 15。理论上如果你用官方的 release 分支就不会有太大的兼容性问题但如果你混用 dev 分支和 release 分支很容易遇到奇奇怪怪的 API 不匹配。磁盘空间这块我强烈建议你准备30GB 以上的可用空间内存建议至少 16GB。如果你只有 8GB 内存也不是不能编但要做好系统卡死的心理准备。编译时间上一个完整的 Release 构建在 12 核 24 线程的机器上大概需要 30 到 60 分钟16G 内存的情况下并行线程不要拉满-j4可能更稳。依赖方面Linux 下需要 gcc 或 clang引导编译器、cmake3.20 以上、ninja、python3、zlib 等。另一点很多人不知道构建 LLVM 需要比较新的 CMake 版本旧版 CMake 可能在配置阶段就报错。如果你用的是 Ubuntu 20.04 那种老系统建议用 pip 装个新版本 colcon 或用源码构建的 cmake。2.2 cmake 配置的几种经典组合官方版、Debug版、Toolchain版CMake 配置是整个构建过程中最需要细抠的一步。这里给出一个我实际用过的、比较合理的组合git clone --depth 1 --branch llvmorg-15.0.7 https://github.com/llvm/llvm-project.git cd llvm-project mkdir build cd build cmake -G Ninja \ -DCMAKE_BUILD_TYPERelease \ -DLLVM_ENABLE_PROJECTSclang;lld;libcxx;compiler-rt \ -DLLVM_TARGETS_TO_BUILDX86;AArch64;RISCV \ -DLLVM_ENABLE_ASSERTIONSON \ -DCMAKE_C_COMPILERgcc \ -DCMAKE_CXX_COMPILERg \ ../llvm ninja逐个解释一下这些参数-DLLVM_ENABLE_PROJECTS指定要构建 llvm-project 仓库里的哪些子项目。clang 是必然要的lld 强烈建议要如果是做 iOS/Android 开发加上 libcxx 和 libcxxabi 会方便很多。但注意不能一开始就加太多比如 mlir 和 flang 如果你不会用到就最好不要加每一个额外项目都会把编译时间往上拉一大截。-DLLVM_TARGETS_TO_BUILD指定后端要支持的目标架构。如果你只有 X86 的工作负载那构建 X86 就够了。如果做交叉编译需要额外加 ARM、AArch64、RISCV 等。target 数量越多代码生成器的代码就越多编译越慢。-DLLVM_ENABLE_ASSERTIONSON打开断言。开发调试模式建议开启能帮你尽早发现 IR 或 pass 中的问题但会损失一部分运行时性能。发布版本建议关闭以获得更快的代码生成速度。-DCMAKE_C_COMPILER/CXX_COMPILER引导编译器一般用系统自带的 gcc 或 clang 都行。没有指定的话 CMake 会自动探测但不同系统默认值不同容易踩坑所以还是显式指定比较好。2.3 构建过程中最常遇到的三类故障及解决记录这类大型项目的编译过程从来不会一帆风顺。我遇到的第一个问题是系统内存不足导致的编译进程崩溃ninja默认会拉满所有核心编译个别巨型文件比如X86ISelDAGToDAG.cpp这种单文件几千行的时内存占用飙到两三个 GB然后 OOM 直接被 kill。解决方法是限制并发度ninja -j4或加-DLLVM_PARALLEL_LINK_JOBS2单独限制链接阶段的并发数。第二个问题是 CMake 版本太老。Ubuntu 18.04 自带的 cmake 3.10 根本跑不起来报错直接说需要 CMake 3.20 以上。这种情况只能用官方脚本装新版本或者用 pip 安装。第三个问题是系统语言环境。我遇到过因为 LANG 不是 en_US.UTF-8 导致测试脚本报错的情况很莫名其妙但把 locale 调成标准 UTF-8 之后就正常了。提示如果你只是想知道构建有没有成功不需要跑完测试套件。测试套件ninja check-all会额外消耗大量时间和资源一般开发场景下没有必要全量跑。构建完成之后build/bin目录下会出现clang、clang、lld、llvm-config等一批可执行文件。llvm-config这个工具很重要它可以帮助你获取 LLVM 的编译参数和链接参数例如llvm-config --cxxflags --ldflags --libs的输出就是后续开发自定义 LLVM 工具时的编译指示。3. 实操把一段 C 代码变成目标的整个过程理解了怎么构建接下来应该亲手走一遍“从源码到机器码”的完整流程。这一步非常重要它能把前面讲的 IR、pass、后端这几个概念串成一条线。其实 LLVM 整个编译过程可以用一条命令拆开来看# 先写个测试文件 test.c # int main() { int a 1; int b 2; return a b; } # 第一步前端 clang 把 C 代码编译成 LLVM IR.ll 文件 clang -S -emit-llvm test.c -o test.ll # 第二步对 IR 执行优化也可以直接用 -O2 一步到位 opt -O2 test.ll -o test.opt.bc # 第三步后端将 IR 生成目标平台的汇编代码 llc test.opt.bc -o test.s # 第四步汇编器转成目标文件 llvm-mc -filetypeobj test.s -o test.o # 第五步链接器生成可执行文件 ld.lld test.o -o test # 也可以用 clang 一步完成所有步骤 clang test.c -o test3.1 从 C 源码到 LLVM IR用 clang 生成可读的中间表示这是最直观理解 IR 的一步。运行clang -S -emit-llvm test.c -o test.ll后你会看到整个 IR 结构。它包含模块信息、全局变量声明、函数定义和基本块。LLVM IR 也是 SSAStatic Single Assignment静态单赋值形式即每个变量只能被赋值一次所以你会看到大量以%1、%2开头的临时变量这是后续做数据流分析和优化的基础。IR 有三种形式人类可读的.ll文本文件、二进制不可读的.bc位码文件以及内存中的表示。三种形式之间可以互相转换这是调试时特别方便的一点——你可以随时把内存中的 IR dump 成文本来看比如在你自己的 pass 里加一句llvm::errs() F;就能把函数的 IR 打印出来。3.2 中间优化层的一个典型 pass 演示自定义一个函数内联观察点LLVM 的优化能力全部体现在 pass 上。自 15 版本开始新 PMNew Pass Manager已经成为了默认方式。编写一个自定义 pass 是了解优化流程最好的方法下面是一个最简单的打印函数名的 pass#include llvm/IR/Function.h #include llvm/IR/LegacyPassManager.h #include llvm/Pass.h #include llvm/IR/Instructions.h #include llvm/IR/Module.h #include llvm/Support/raw_ostream.h namespace { struct MyPass : public llvm::FunctionPass { static char ID; MyPass() : llvm::FunctionPass(ID) {} bool runOnFunction(llvm::Function F) override { llvm::errs() Function: F.getName() \n; for (auto BB : F) { for (auto I : BB) { llvm::errs() Inst: I.getOpcodeName() \n; } } return false; } }; } // namespace char MyPass::ID 0; static llvm::RegisterPassMyPass X(mypass, My Pass);然后用llvm-config编译成一个动态库或静态链接的工具试试对前面生成的test.ll跑一遍clang -fPIC -shared mypass.cpp llvm-config --cxxflags -o libmypass.so opt -load-pass-plugin./libmypass.so -passesmypass test.ll这个虽然简单但你已经可以在这个框架里做任何你想做的变换了——比如统计某个函数的循环数量、插桩记录调用次数、做死代码消除等。理解了如何编写和使用 pass你就掌握了修改编译器行为最核心的手段。3.3 机器码生成看 llc 如何做指令选择、寄存器分配和指令调度IR 优化完之后最后一步就是交给后端变成真正的机器指令。以 x86 平台为例输入的 IR 会经过层层转换才变成movl、addl这样的指令。这个过程中有三个关键步骤非常值得一提指令选择Instruction Selection把 IR 上的模式匹配成目标平台的指令模式。比如add nsw i32 %1, %2这个 IR会被 x86 后端匹配成addl %r1, %r2指令。寄存器分配Register Allocation把 IR 里无限的虚拟寄存器映射到有限的物理寄存器上。这是编译领域最经典的 NP 问题之一LLVM 用的是一种叫 Greedy 的启发式算法在实际中效果非常好。指令调度Instruction Scheduling重新排列指令顺序使得 CPU 流水线能够更高效地执行减少停顿。你可以用llc -debug看到整个后端的调试输出注意要打开-DLLVM_ENABLE_ASSERTIONS并有 debug 符号也能用llc -show-mc-inst查看生成的机器码。这个层面在工作日常中可能不常用到但理解它有助于你分析后端的性能调优。4. llvmpipe 和 256 bitsLLVM JIT 与 SIMD 的亲密关系接着前面提到 llvmpipe 这个话题。Mesa 是 Linux 图形栈里的一个开源实现库它实现 OpenGL、Vulkan 等图形 API。正常流程下GPU 驱动程序收到渲染指令后会把着色器编译成 GPU 能识别的微码或硬件指令去执行。但如果你没有 GPU或者 GPU 驱动未加载程序不就应该没法跑了吗llvmpipe 的路子是我不跑在 GPU 上我直接在 CPU 上把着色器“翻译”成高效的机器码来跑用 CPU 的速度来模拟 GPU 的渲染管线。4.1 llvmpipe 为什么不直接用 C 写渲染器而是要引入 LLVM如果你只是像一个普通的软件渲染器那样把每个像素点的颜色计算用 C 语言循环一遍性能会非常差。因为 GPU 着色器里大量用到向量运算vec4 这样的四分量浮点向量而 CPU 恰好也有对应的向量指令——SSE 能一次计算 128 位即 4 个 floatAVX2 能一次算 256 位即 8 个 floatAVX-512 甚至能一次处理 512 位。问题是你不可能预先知道用户会提交什么样的着色器所以没法在编译 libgallium 时就把着色器逻辑写死。这时候 LLVM 的 JIT 特性就派上用场了在运行时把着色器 IR 动态编译成当前 CPU 支持的最合适的 SIMD 指令。这也是为什么你会在日志里看到llvmpipe (llvm 15.0.7, 256 bits)这样的标识——它告诉你当前这套 llvmpipe 是绑定 LLVM 15.0.7 构建的它现在使用 256 位的向量宽度也就是 AVX2 指令集。如果你的 CPU 支持 AVX-512llvmpipe 可以尝试用 512 位宽度取决于编译时的配置和运行时检测。向量宽度越大一次循环同时处理的像素或顶点数据就越多渲染性能越好。4.2 在 llvmpipe 中 LLVM 的 JIT 发挥了什么作用一条完整路径用更技术的方式描述这个过程llvmpipe 里的一个组件会收到来自 Gallium3D 状态的 TGSI 或 NIR两种中间表示语言然后通过 LLVM 的 IR builder 接口把着色器翻译成对应的 LLVM IR。之后调用 LLVM 的优化 pass例如Verifier、InstructionCombining等再交给某个 TargetMachine如 X86 的X86TargetMachine做代码生成。最终通过ExecutionEngine执行代码生成的函数的调用指针把对着色器代码的调用转化成对一块 CPU 指令区段的调用。这里面有一个非常关键的设计点因为 CPU 的 SIMD 指令是定宽的AVX2 是 256 位而着色器的向量类型可能是任意的vec2、vec3、vec4llvmpipe 需要高效地做“向量的 fill 和 extract”。这部分逻辑完全由 LLVM 优化器帮忙处理。写软件渲染器的人不用操心指令选择只要描述“这里有四个 float 需要相加”LLVM 后端就会自动帮你选好vaddps ymm0, ymm1, ymm2AVX2 下的 256 位浮点向量加指令还是拆成两个 128 位的vaddps xmm0, xmm1, xmm2看目标 CPU 的支援情况来定。4.3 向量宽度 256 bits 意味着什么从实际帧率角度看有些用户可能会纠结为什么是 256 bits 而不是 128 bits这个直接决定了每周期能处理多少数据。在当前消费级 CPU 上AVX2 是最普及的向量指令集基本上 Ryzen 和 4 代以后的 Core 都支持。llvmpipe 把向量宽度设为 256 bits代表它一次可以并行执行 8 个单精度浮点数的运算比 128 bits 翻了一倍。如果你的机器有 AVX-512比如服务器级至强、部分 11 代以后至强理论上 llvmpipe 还能启用 512 位模式但这种场景在真机上并不常见也受功耗限制。我实际用 llvmpipe 跑过一些测试说实话在室内分辨率较低的窗口环境下、跑简单的图形程序llvmpipe 是能扛得住的。但到重度 3D 场景比如用 Godot 开启软件渲染帧率就会比较难看了。“256 bits”加快了每像素的吞吐但毕竟 CPU 并行度和 GPU 无法相提并论——LLVM 已经把 CPU 的能力榨到接近极限物理天花板在那里。5. 学会用 LLVM 能做哪些实际事情一些值得落地的应用方向如果你对编译技术本身不感兴趣可能不知道 LLVM 到底能帮你做什么实际项目。这一节我举几个真实场景它们都没有超出 LLVM 的能力圈但却能帮你看到“这玩意不是只有理论价值”。5.1 为自研语言写一个快速编译器前端和后端现在很多语言开发者在设计新语言时一开始就定了“使用 LLVM 作后端”。Rust 用的是自研前端 LLVM 后端Julia 用 LLVM 做 JITSwift 也用 LLVM。你写一门新语言时最吃力的其实是后端——要生成高质量的、支持各种平台的机器码需要数年时间。用 LLVM前端把源代码翻译成 IR后端所有优化和平台支持都已经给你准备好了。前端的工作量也大多集中在“语义分析”和“IR 生成”上。你用LLVMContext创建模块用IRBuilder创建指令几乎不需要考虑寄存器和指令选择。一个简单支持整数的语言的编译器几千行 C 代码就能搞定——这个投入产出比在非 LLVM 时代是不可想象的。5.2 实现一个静态分析工具LLVM 提供了一套完整的 C API 来遍历 IR、分析数据流和控制流。市面上很多静态分析工具如 Clang Static Analyzer、Infer 的一部分都基于它。如果你想扫描自己项目的潜在崩溃点比如空指针解引用、未初始化变量完全可以基于 clang 的前端 LLVM 的 IR 做定制化分析。在opt里加一个自定义 pass设置某个分析策略再跑一遍目标源代码就可以在 IR 层做检查。这也顺便解释了为什么 LLVM 在 DevSecOps 赛道如此重要。5.3 做高性能计算相关的优化MLIR 和多面体优化器 Polly 在科学研究中大放异彩比如针对矩阵运算、卷积、FFT 等计算核的自动向量化和缓存分块优化。这些优化在普通编译器里很难做因为需要分析循环嵌套的数学结构但 Polly 在 LLVM IR 层做了一个抽象可以将循环变换成多维数据流模型然后做极致的并行化和数据局部性优化。此时 256 bits 的 SIMD 宽度不再是 GPU 专用词汇——它在 CPU 的高性能计算里也非常重要。6. 问题排查速查表那些“我也遇到过的”典型问题最后总结我实际碰过的几个常见坑直接整理成一张表格供你对照排查。现象可能的原因解决方法cmake 报错CMake 3.20 required系统 cmake 版本过老用 pip 安装新版本或从 cmake.org 下载编译时提示fatal error: llvm/IR/Function.h file not found未正确使用 llvm-config 的 cxxflags检查llvm-config --includedir路径是否加入编译参数链接时大量 undefined reference漏掉 LLVM 的库参数使用llvm-config --libs --system-libs补充链接参数opt报错Pass plugin not found插件编译为动态库时缺少位置无关代码编译时加-fPIC跑测试check-all卡死并发过高导致资源耗尽或死锁降低并行数或单独跑特定测试目录使用 llvmpipe 画面花屏或崩溃llvmpipe 版本与 LLVM 版本不匹配确认 Mesa 官方对该 LLVM 版本的支持情况不要混用 dev 分支构建时间过长打开了太多 target 和 project用-DLLVM_TARGETS_TO_BUILD精简目标平台只保留需要的6.2 一个提升调试效率的小技巧学会 dump 和看 IR最后再分享一个我使用频率最高的调试技巧碰到任何编译或优化相关的问题第一步永远是“把 IR 文件 dump 出来看一遍”。无论是你想验证 clang 有没有按预期往前走还是想观察 optimizer 有没有做某些优化都建议看一下 IR 的实际形态。你可以用clang -S -emit-llvm -O0看未优化的原始 IR用clang -S -emit-llvm -O2看优化后的形态对两个文件跑一下diff很多问题就一目了然了。如果你在写自己的 pass一定要在 pass 前和 pass 后分别 dump 一次再对比。这种“前后对照”的调试习惯比单纯看代码高效十倍。我个人在实际操作中的体会是LLVM 的学习曲线不低但它的回报非常高。你不需要一开始就把 IR 规范、pass 管理、目标后端这些全部啃完可以先从把 C 编译成 IR开始然后再改一个 pass加一条指令最后再考虑深入后端或 JIT。这套项目的架构很优雅但正因为功能太多你反而要学着“克制”地使用它——抓住一条自己需要的链路吃透它比试图理解全部细节要有效得多。希望这篇基于 llvm-project 的拆解能帮你少走一些我当年走过的弯路。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/9/19 14:53:09
编译器自举实战:从种子编译器到字节级对拍
2026/9/19 14:48:09
AI时代PLC工程师的生存法则:从写代码到搞定产线
2026/9/19 14:48:09
MDPI投稿状态全解析:11个状态含义、时间线与催稿技巧
2026/9/19 18:13:22
Matter(connectedhomeip)TI CC13x4_CC26x4 平台 OpenThread 库构建配置完全指南
2026/9/19 18:13:22
PyTorch Lightning 15 分钟上手指南:从零构建自编码器训练全流程
2026/9/19 18:13:22
RIOT OS 中 JC42 兼容温度传感器驱动测试:从配置参数到源码级原理
2026/9/19 18:13:22
美团mtgsig与waimai_sign动态签名逆向解析实战
2026/9/19 18:13:22
2026年13款主流性能测试工具选型指南:从JMeter到k6的实战对比
2026/9/19 18:08:22
StarRocks truncate 函数详解:向零方向截断小数位的数值处理实战
2026/9/19 0:02:13
PixiJS v8 遮罩(Masking)完全指南:AlphaMask、StencilMask、ScissorMask 与 ColorMask
2026/9/19 0:02:13
GLM 5.3 Flash 被 Artificial Analysis 收录:用 TaoToken 复现同一把 Key
2026/9/19 0:02:13
分布式雷达多维度干扰建模与抗干扰算法实现
2026/9/18 16:05:49
拯救者Y7000黑屏故障排查与维修实战指南
2026/9/18 3:56:12
AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验
2026/9/18 13:25:13
Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化