LLVMSwift:用Swift原生封装LLVM,实现类型安全的编译器开发
1. 项目概述与核心价值如果你是一个 Swift 开发者同时对编译原理、程序分析或者高性能计算感兴趣那么你很可能听说过 LLVM。这个强大的编译器基础设施几乎无处不在从 Clang 到 Swift 编译器本身再到各种 JIT 引擎背后都有它的身影。然而直接使用 LLVM 的 C API 对于 Swift 开发者来说体验并不那么“Swift”——你需要处理繁琐的 C 桥接、手动内存管理以及两套截然不同的编程范式。这正是LLVMSwift诞生的原因。它是一个用纯 Swift 编写的、对 LLVM C API 进行封装的开源库旨在为 Swift 社区提供一个原生、类型安全且符合 Swift 习惯的编译器开发工具包。简单来说LLVMSwift 让你能用 Swift 语言流畅地操作 LLVM 中间表示IR、构建函数、控制流程甚至实现即时编译JIT而无需离开你熟悉的 Swift 环境。它不仅仅是一个简单的绑定更是一个精心设计的抽象层将 LLVM 复杂的概念映射为直观的 Swift 类型和方法。无论是想为你的 DSL领域特定语言创建一个后端还是想构建一个静态分析工具或者仅仅是学习编译器的内部工作原理LLVMSwift 都提供了一个绝佳的起点。它降低了编译器开发的门槛让“用 Swift 写编译器”从一个想法变成了可以轻松上手的工程实践。2. 核心设计理念与架构解析LLVMSwift 的设计哲学非常明确在提供对 LLVM 完整能力访问的同时坚守 Swift 的语言特性和开发体验。这听起来简单实现起来却需要解决诸多挑战比如类型系统的映射、内存生命周期的管理以及如何将 LLVM 的指令式、基于上下文的 API 转换成 Swift 更函数式、更安全的风格。2.1 类型安全的 IR 构建LLVM IR 是一种强类型的、低级的中间语言。在 C API 中类型通常通过指针和动态转换来操作这容易引发运行时错误。LLVMSwift 的核心突破在于它用 Swift 的协议和泛型系统为 LLVM 的类型建立了一个静态的类型安全层。例如LLVM 中的i3232位整数、double双精度浮点数、i64*指向64位整数的指针都是不同的类型。在 LLVMSwift 中它们被表示为遵循IRType协议的具体结构体如IntType、FloatType、PointerType。当你创建一个常量或进行运算时编译器会在编译期检查类型的正确性。let int32 IntType.int32 let double FloatType.double let constInt int32.constant(42) // 正确创建一个 i32 常量 42 // let sum builder.buildAdd(constInt, double.constant(3.14)) // 编译错误不能将整数和浮点数相加这种设计极大地减少了因类型不匹配导致的隐蔽 Bug。IRBuilder的各个buildXXX方法如buildAdd,buildLoad,buildStore都利用了泛型约束确保你只能在兼容的类型上进行操作。2.2 基于上下文Context与构建器Builder的编程模型LLVM 的 API 设计是围绕LLVMContext和IRBuilder展开的。LLVMSwift 忠实地反映了这一点但做了符合 Swift 习惯的简化。Module模块这是 IR 的顶级容器对应一个编译单元如一个 .c 文件。在 LLVMSwift 中创建模块时它会自动管理底层的LLVMContext。IRBuilderIR 构建器这是生成指令的核心工具。你可以把它想象成一个“光标”或“插入点”在某个基本块Basic Block内移动并在当前位置插入指令。LLVMSwift 的IRBuilder将 LLVM 中需要传入大量上下文参数的函数转换成了流畅的链式或方法调用。let module Module(name: “MyModule”) let builder IRBuilder(module: module) // 定义一个函数返回类型为 i32接受两个 i32 参数 let funcType FunctionType([IntType.int32, IntType.int32], IntType.int32) let function builder.addFunction(“add”, type: funcType) // 创建入口基本块并将构建器定位到其末尾 let entry function.appendBasicBlock(named: “entry”) builder.positionAtEnd(of: entry) // 获取函数参数并生成加法指令 let arg1 function.parameters[0] let arg2 function.parameters[1] let result builder.buildAdd(arg1, arg2, name: “sum”) builder.buildRet(result)这段代码清晰地展示了从模块、函数到基本块和指令的构建流程。IRBuilder负责记录当前的位置和上下文你只需要关心“插入什么”而不用反复传递“插入到哪里”。2.3 对 SSA 形式和 PHI 节点的原生支持静态单赋值形式是 LLVM IR 的一个关键特性它要求每个变量只被赋值一次这极大地简化了程序分析和优化。然而当控制流交汇时例如 if-else 语句之后同一个变量在不同路径上可能有不同的值这就需要引入PHI 节点来合并这些值。PHI 节点对于初学者来说是一个难点但 LLVMSwift 的 API 让它变得直观。IRBuilder提供了buildPhi方法来创建 PHI 节点然后你可以为其添加来自不同前驱基本块的“传入值”。// 假设我们有两个基本块 thenBB 和 elseBB它们都会跳转到 mergeBB builder.positionAtEnd(of: mergeBB) let phi builder.buildPhi(IntType.int32, name: “mergedValue”) // 添加来自 thenBB 的值为 valueFromThen来自 elseBB 的值为 valueFromElse phi.addIncoming([(valueFromThen, thenBB), (valueFromElse, elseBB)]) // 现在 phi 这个值在 mergeBB 中就可以使用了它的具体值取决于控制流是从 thenBB 还是 elseBB 过来的。这个设计将 PHI 节点这个抽象概念转化为了对元组数组的简单操作非常符合 Swift 的数据操作习惯。2.4 内存管理与所有权LLVM 的 C API 使用手动引用计数来管理 IR 对象的内存。LLVMSwift 通过 Swift 的自动引用计数来包装这些对象大部分情况下你无需关心内存的释放。然而理解一些边界情况很重要当 Swift 对象被释放时其底层的 LLVM 对象也会被释放。这意味着你必须确保在 IR 被使用例如被 JIT 执行或被写入文件期间对应的 Swift 对象保持在作用域内。对于IRBuilder、Module等主要对象通常你会让它们作为某个长期存在的控制器或上下文对象的属性从而保证其生命周期。这是一种更“Swift”的资源管理方式相比 C 的显式new/delete或std::unique_ptr心智负担更小。3. 核心功能深度解析与实操了解了设计理念后我们深入到几个核心功能模块看看如何用 LLVMSwift 完成实际的编译器任务。3.1 从零构建一个简单的函数让我们构建一个计算阶乘的函数factorial(i32) - i32。这是一个经典的递归/循环示例但为了清晰我们先实现循环版本。import LLVM let module Module(name: “FactorialModule”) let builder IRBuilder(module: module) // 1. 定义函数类型接受一个 i32返回一个 i32 let i32 IntType.int32 let factorialType FunctionType([i32], i32) // 2. 在模块中创建函数 let factorialFunc builder.addFunction(“factorial”, type: factorialType) // 为函数的参数命名方便后续引用 let n factorialFunc.parameters[0] n.name “n” // 3. 创建基本块entry检查参数、loop循环体、after_loop返回结果 let entryBB factorialFunc.appendBasicBlock(named: “entry”) let loopBB factorialFunc.appendBasicBlock(named: “loop”) let afterLoopBB factorialFunc.appendBasicBlock(named: “after_loop”) // 4. 在 entry 块检查 n 1如果是直接返回 1否则跳转到循环 builder.positionAtEnd(of: entryBB) let one i32.constant(1) let nLe1 builder.buildICmp(n, one, .signedLessThanOrEqual) // 有符号比较 n 1 builder.buildCondBr(condition: nLe1, then: afterLoopBB, else: loopBB) // 5. 在 after_loop 块直接返回 1处理 n 1 的情况 builder.positionAtEnd(of: afterLoopBB) builder.buildRet(one) // 6. 在 loop 块实现循环计算 fact fact * i, i 从 n 递减到 2 builder.positionAtEnd(of: loopBB) // 我们需要两个“变量”循环计数器 i 和累积结果 acc。在 SSA 中它们需要是 PHI 节点。 let iPhi builder.buildPhi(i32, name: “i”) let accPhi builder.buildPhi(i32, name: “acc”) // 初始化 PHI 节点从 entry 块过来时i n, acc 1 iPhi.addIncoming([(n, entryBB)]) accPhi.addIncoming([(one, entryBB)]) // 计算 i - 1 let iNext builder.buildSub(iPhi, one, name: “i.next”) // 计算 acc * i let accNext builder.buildMul(accPhi, iPhi, name: “acc.next”) // 判断 iNext 是否 1 let continueCond builder.buildICmp(iNext, one, .signedGreaterThan) // 创建循环内的“合并”块用于更新 PHI 节点的下一次迭代值 let loopEndBB factorialFunc.appendBasicBlock(named: “loop.end”) builder.buildCondBr(condition: continueCond, then: loopEndBB, else: afterLoopBB) // 7. 在 loop.end 块为下一次循环设置 PHI 节点的传入值并跳回 loop 块头部 builder.positionAtEnd(of: loopEndBB) iPhi.addIncoming([(iNext, loopEndBB)]) accPhi.addIncoming([(accNext, loopEndBB)]) builder.buildBr(loopBB) // 注意在 afterLoopBB 中我们需要返回 accPhi 的值而不是常量 1。 // 所以需要修改 afterLoopBB它现在有两个前驱entry 和 loop需要另一个 PHI 节点来决定返回值。 // 我们先清空并重建 afterLoopBB 来演示一个更完整的方案。 // 更优的做法是在构建流程上稍作调整这里为了演示 PHI我们采用以下方式 // 在 afterLoopBB 开始时插入一个 PHI 节点接收来自 entryBB 的 1 和来自 loopBB 的 accPhi。 builder.positionAtEnd(of: afterLoopBB) let resultPhi builder.buildPhi(i32, name: “result”) // 我们需要知道 afterLoopBB 的前驱块。在这个设计中afterLoopBB 在构建时其前驱还不完整。 // 这揭示了编译器 IR 构建的一个常见模式有时需要先创建块再通过后序遍历来连接 PHI。 // 一个更简单的实现是避免在 afterLoopBB 中使用 PHI而是让 entryBB 直接返回 1loopBB 在条件不满足时直接返回 accNext。 // 我们重构一个更清晰的版本注意上面的例子故意展示了一个复杂情况揭示了在单次线性遍历中构建带有 PHI 节点的循环控制流时的挑战。在实际操作中一种更常见的模式是entryBB判断n 1若真跳转到returnOneBB直接返回1若假跳转到loopPreheaderBB。在loopPreheaderBB中初始化循环归纳变量然后跳入loopBB。loopBB的 PHI 节点接收来自loopPreheaderBB初始值和loopLatchBB下一次迭代值的传入值。在循环体末尾判断条件若继续跳转到loopLatchBB更新变量并跳回loopBB若结束跳转到exitBB。exitBB包含一个 PHI 节点接收来自returnOneBB值为1和loopBB值为最终累积值的传入值然后统一返回。这个例子说明了虽然 LLVMSwift 提供了友好的 API但构建正确的 SSA 形式仍然需要你对控制流图有清晰的理解。建议在纸上画出基本块和跳转关系后再开始编码。3.2 类型系统详解与操作LLVMSwift 的IRType协议体系非常完备。理解这些类型是有效生成 IR 的关键。基本类型VoidType、IntType包括i1布尔类型、FloatTypefloat、double等。派生类型PointerType通过pointee属性获取其指向的类型。例如IntType.int64.pointerType()创建i64*。ArrayType固定长度的同构数组如ArrayType(elementType: i32, count: 10)创建[10 x i32]。StructType异构结构体。可以创建字面量结构体StructType(elementTypes: [i32, double], isPacked: false)或具名结构体。FunctionType函数类型由参数类型列表和返回类型定义。VectorTypeSIMD 向量类型。类型之间的转换必须显式进行LLVMSwift 在IRBuilder中提供了相应的转换指令let intVal: IRValue i32.constant(42) let doubleVal: IRValue FloatType.double.constant(3.14) // 整数到浮点的有符号转换 let intToFloat builder.buildSIToFP(intVal, type: FloatType.double, name: “int2float”) // 浮点到整数的截断转换向零取整 let floatToInt builder.buildFPToSI(doubleVal, type: IntType.int32, name: “float2int”) // 整数位扩展零扩展或符号扩展 let int64Val builder.buildZExt(intVal, type: IntType.int64, name: “zext”) // 零扩展 // builder.buildSExt // 符号扩展 // 指针类型转换 let i64Ptr PointerType(pointee: IntType.int64) let genericPtr PointerType.toVoid let bitcastPtr builder.buildBitCast(intVal, type: genericPtr, name: “bitcast”)3.3 利用 JIT 执行生成的代码生成 IR 之后最激动人心的莫过于立刻执行它。LLVMSwift 的JIT模块让这变得非常简单。它抽象了 LLVM 的 ORC JIT API提供了添加模块、查找符号和获取函数指针的能力。import LLVM // 1. 创建一个简单的模块函数返回 42 let module Module(name: “SimpleJIT”) let builder IRBuilder(module: module) let function builder.addFunction(“answer”, type: FunctionType([], IntType.int32)) let entry function.appendBasicBlock(named: “entry”) builder.positionAtEnd(of: entry) builder.buildRet(IntType.int32.constant(42)) // 2. 初始化 JIT。需要指定一个 TargetMachine它代表了目标平台的特性如 CPU 架构、操作系统。 // 通常可以使用默认的本地机器。 guard let targetMachine try? TargetMachine() else { fatalError(“无法创建目标机器。请确保 LLVM 已正确安装且 llvm-config 在 PATH 中。”) } do { // 3. 创建 JIT 实例 let jit try JIT(machine: targetMachine) // 4. 将模块添加到 JIT 中。addEagerlyCompiledIR 会立即编译整个模块。 _ try jit.addEagerlyCompiledIR(module) { (moduleName) - JIT.TargetAddress in // 这是一个符号解析回调。如果模块中的代码引用了外部函数如 printf // 你需要在这里返回该函数在进程中的地址。 // 对于不引用外部符号的简单模块可以返回一个空地址。 return JIT.TargetAddress() } // 5. 查找我们生成的函数的地址 let funcAddr try jit.address(of: “answer”) // 6. 将地址转换为可调用的函数指针 typealias AnswerFunc convention(c) () - Int32 let answerFunc unsafeBitCast(funcAddr, to: AnswerFunc.self) // 7. 调用它 let result answerFunc() print(“The answer is: \(result)”) // 输出The answer is: 42 } catch { print(“JIT 执行失败: \(error)”) }实操心得JIT 执行看似简单但有几个坑需要注意。第一符号解析如果你的 IR 代码调用了外部函数比如printf你必须在addEagerlyCompiledIR的回调中提供这些函数的真实地址否则 JIT 链接时会失败。你可以使用dlsym来查找标准库中的函数地址。第二内存管理JIT 编译后的代码位于可执行的内存页中。当JIT对象被释放时这些内存会被自动回收。确保在调用完函数之前不要意外释放JIT实例或包含其的父对象。第三线程安全默认的JIT不是线程安全的。如果需要在多线程环境下并发编译或执行需要深入研究 ORC JIT 的层LLJIT、IRTransformLayer等LLVMSwift 也提供了对这些底层 API 的访问但复杂度更高。4. 项目集成与构建实战将 LLVMSwift 集成到你的 Swift 项目中是开始实际开发的第一步。虽然 README 提供了指南但在不同平台和配置下你可能会遇到一些挑战。4.1 基于 Swift Package Manager 的集成推荐这是最简洁的方式。在你的Package.swift文件中添加依赖// swift-tools-version:5.5 import PackageDescription let package Package( name: “MyCompiler”, platforms: [.macOS(.v10_15)], // LLVMSwift 可能对 macOS 版本有要求 dependencies: [ .package(url: “https://github.com/llvm-swift/LLVMSwift.git”, from: “0.13.0”), // 使用最新稳定版 ], targets: [ .executableTarget( name: “MyCompiler”, dependencies: [“LLVMSwift”]), .testTarget( name: “MyCompilerTests”, dependencies: [“MyCompiler”]), ] )关键前提是系统上必须安装 LLVM。SPM 会通过pkg-config来查找 LLVM。安装 LLVM 与配置 pkg-config安装 LLVM使用 Homebrew (macOS) 或系统包管理器。# macOS brew install llvm15 # 注意brew 安装的 llvm 可能版本号较高如 llvm16。请确保与 LLVMSwift 版本兼容。 # Ubuntu/Debian sudo apt-get install llvm-15-dev clang-15 libclang-15-dev确保llvm-config在 PATH 中# Homebrew 通常不会自动链接到 /usr/local/bin因为存在系统冲突。 # 你需要将其添加到 PATH或使用完整路径。 export PATH“/usr/local/opt/llvm15/bin:$PATH” which llvm-config # 应输出类似 /usr/local/opt/llvm15/bin/llvm-config生成 pkg-config 文件这是最关键的一步。LLVMSwift 仓库中的utils/make-pkgconfig.swift脚本可以帮你。# 克隆 LLVMSwift 仓库或者你已经作为依赖下载了它 git clone https://github.com/llvm-swift/LLVMSwift.git cd LLVMSwift # 运行脚本它会读取 llvm-config 的输出并生成 .pc 文件 swift utils/make-pkgconfig.swift脚本会询问你将.pc文件安装到哪里。通常选择1安装到/usr/local/lib/pkgconfig或2安装到~/.local/lib/pkgconfig。你需要确保pkg-config能在这个目录找到它。安装后可以运行pkg-config --libs llvm来测试。完成这些步骤后回到你的项目目录运行swift buildSPM 应该能成功解析并编译 LLVMSwift 依赖。4.2 常见构建问题排查错误could not find LLVMConfig.cmake或llvm-config not found原因llvm-config不在PATH中或者pkg-config文件未正确生成/安装。解决确认llvm-config的完整路径并临时或永久地将其加入PATH。运行llvm-config --version确认版本。LLVMSwift 通常要求一个较新的版本如 11.0。重新运行make-pkgconfig.swift并确保将生成的.pc文件安装到了pkg-config的搜索路径中。可以通过pkg-config --variable pc_path pkg-config查看搜索路径。手动检查生成的.pc文件内容确保Libs和Cflags字段指向正确的 LLVM 库和头文件路径。错误missing required module ‘LLVMSupport’或链接错误原因LLVM 是一个庞大的库由许多子组件构成。pkg-config 文件可能没有包含所有必要的链接库。解决这通常是因为 LLVM 的安装方式不同比如从源码编译时开启了特定组件。你可以尝试修改生成的.pc文件在Libs行添加更多组件例如-lLLVMSupport -lLLVMCore -lLLVMIRReader …。一个更简单粗暴但有效的方法是使用llvm-config --libs all --system-libs的输出替换.pc文件中的Libs行但这可能会链接进许多未用到的库。在 Xcode 中使用 SPM如果你在 Xcode 中打开 SPM 项目Xcode 会自己处理依赖。但同样需要保证系统层面的llvm-config和pkg-config配置正确。有时 Xcode 使用的环境变量与终端不同可能导致找不到pkg-config。你可以在 Xcode 的 scheme 设置中为可执行目标添加PKG_CONFIG_PATH环境变量指向你的.pc文件所在目录。4.3 手动集成非 SPM 项目对于不使用 SPM 的现有项目例如一个传统的 Xcode 项目手动集成是可行的但更繁琐。添加源码将 LLVMSwift 的Sources/LLVMSwift目录下的所有.swift文件拖入你的 Xcode 项目。配置头文件搜索路径在项目的Build Settings中将Header Search Paths设置为llvm-config --includedir命令的输出。这确保了 Swift 编译器能找到 LLVM 的 C 头文件通过桥接。配置库搜索路径将Library Search Paths设置为llvm-config --libdir的输出。链接动态库在Link Binary With Libraries构建阶段添加libLLVM.dylibmacOS或libLLVM.soLinux。你需要导航到llvm-config --libdir指示的目录下去找到这个库文件。更稳妥的方法是添加$(LIBRARY_SEARCH_PATHS)/libLLVM.dylib。配置其他链接器标志你可能还需要在Other Linker Flags中添加-lLLVM并确保链接了所有必要的子系统库如-lc。注意事项手动集成极易出错尤其是处理 LLVM 复杂的依赖关系时。强烈建议尽可能将你的项目迁移到 Swift Package Manager它能自动处理这些复杂的编译和链接设置。如果项目结构确实无法使用 SPM可以考虑将 LLVMSwift 及其依赖打包成一个动态框架然后再让你的主项目依赖这个框架这能将复杂度隔离。5. 高级应用场景与性能考量掌握了基础之后我们可以探索一些更高级的应用场景并讨论性能相关的最佳实践。5.1 实现一个简单的解释器或 DSL 后端假设你设计了一个简单的计算器语言支持变量和四则运算。你的前端解析器会生成抽象语法树AST。利用 LLVMSwift你可以轻松地将其编译成机器码。符号表管理你需要一个字典来将变量名映射到 LLVM IR 中的AllocaInst栈上分配或IRValue。对于简单的解释器每个函数作用域可以维护一个独立的符号表。递归下降代码生成为每种 AST 节点类型编写一个代码生成方法。例如visitBinaryExpr会根据操作符调用builder.buildAdd、buildSub等。函数调用如果支持用户自定义函数你需要处理函数声明builder.addFunction和函数调用builder.buildCall。注意管理调用约定和参数传递。优化生成原始 IR 后你可以利用 LLVM 内置的优化管道。LLVMSwift 提供了PassPipeliner等工具来运行一系列优化 pass如-O1,-O2。在 JIT 场景下运行优化可以显著提升最终代码的执行速度。// 伪代码示例为二元表达式生成代码 func visit(_ expr: BinaryExpr) - IRValue { let lhs visit(expr.left) // 递归生成左子表达式代码 let rhs visit(expr.right) // 递归生成右子表达式代码 switch expr.op { case .plus: return builder.buildAdd(lhs, rhs, name: “addtmp”) case .minus: return builder.buildSub(lhs, rhs, name: “subtmp”) case .multiply: return builder.buildMul(lhs, rhs, name: “multmp”) case .divide: // 注意整数除法与浮点数除法指令不同 return builder.buildSDiv(lhs, rhs, name: “divtmp”) } }5.2 静态分析与 IR 遍历LLVM IR 本身是一个丰富的数据结构。你可以使用 LLVMSwift 来遍历和分析已有的模块例如从 bitcode 文件加载而不只是生成新的 IR。import LLVM // 从 bitcode 文件加载模块需要 LLVM 的 Bitcode 解析支持 let context Context() guard let memoryBuffer try? MemoryBuffer(filename: “input.bc”) else { fatalError(“无法读取文件”) } guard let module try? context.parseBitcode(from: memoryBuffer) else { fatalError(“解析 bitcode 失败”) } // 遍历模块中的所有函数 for function in module.functions { print(“函数名: \(function.name)”) // 遍历函数中的所有基本块 for block in function.basicBlocks { print(“ 基本块: \(block.name ?? “unnamed”)”) // 遍历基本块中的所有指令 for inst in block.instructions { print(“ 指令: \(inst)”) // 你可以检查指令的具体类型 if let callInst inst as? CallInst { print(“ 调用了函数: \(callInst.calledFunction?.name ?? “indirect”)”) } } } }这对于构建 Linter、度量工具如计算循环复杂度、或者进行简单的代码转换非常有用。5.3 性能优化与陷阱重用 IRBuilder 和 Context创建IRBuilder和Context是有开销的。在一个编译会话中尽量重用它们而不是为每个函数创建新的实例。谨慎使用dump()module.dump()或value.dump()在调试时非常方便但它们会输出到标准错误流并且可能不是线程安全的。在性能关键的路径或生产日志中应避免使用。理解 IR 的不可变性LLVM IR 中的许多对象在创建后是部分不可变的。例如你不能直接修改一个FunctionType的参数列表。这种设计鼓励了函数式编程风格即通过创建新的对象来组合出复杂的结构。内存与生命周期虽然 Swift ARC 管理了 Swift 对象但 LLVM 底层对象之间的引用关系复杂。确保你的 Swift 对象图特别是持有Module、Function引用的对象的生命周期覆盖了整个 IR 的使用期如优化、JIT 编译、执行。在 JIT 场景下一个常见的错误是模块在编译后被提前释放。多线程LLVM 的上下文LLVMContext通常是线程不安全的。如果你需要并行生成多个不相关的模块可以为每个线程创建独立的Context和Module。然而IRBuilder本身不是线程安全的即使在不同模块间共享也需要同步。LLVMSwift 将 Swift 的优雅与 LLVM 的强大连接了起来为 Swift 生态打开了一扇通往底层编程和编译器领域的大门。从教育目的的小型解释器到需要高性能代码生成的专业工具它都是一个值得深入探索的利器。开始你的项目时不妨从复制和修改官方示例入手逐步理解 IR 的构造规则最终你将能自如地运用 Swift 来驾驭 LLVM 这座编译器领域的宝藏。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2561550.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!