深入 Compiler Explorer 构建系统架构:从 CMake 专用实现到可插拔的 BuildSystemDriver

发布时间:2026/9/20 12:02:31
深入 Compiler Explorer 构建系统架构:从 CMake 专用实现到可插拔的 BuildSystemDriver 深入 Compiler Explorer 构建系统架构从 CMake 专用实现到可插拔的 BuildSystemDriver【免费下载链接】compiler-explorerRun compilers interactively from your web browser and interact with the assembly项目地址: https://gitcode.com/gh_mirrors/co/compiler-explorerCompiler Explorer 的 IDEtree模式可以把整个项目交给构建系统处理而不是仅对单个源文件调用编译器目前官方支持 CMake、Cargo、Maven 与 Make 四种构建系统。本文以 docs/BuildSystems.md 为骨架结合 lib/base-compiler.ts 与 lib/build-systems/ 下的驱动实现源码完整讲解这条机制的后端编排流程、前端请求路径、驱动接口契约以及项目如何分阶段把当初硬编码的cmake()路径演进为可插拔的BuildSystemDriver架构。读完你将能回答一个项目构建请求在 Compiler Explorer 内部如何流转、每种构建系统各有哪些独有处理、为什么 CMake 的旧实现无法直接泛化以及未来新增构建系统如 Gradle时需要在哪些层面动手。背景tree 模式为什么需要构建系统Compiler Explorer 的常规模式面向单个源文件用户粘贴一段代码后端直接调用编译器产出汇编。但在 treeIDE模式下用户管理的是整个项目——一个清单文件manifest加上若干附加文件例如CMakeLists.txt、Cargo.toml、pom.xml或Makefile及其配套源码。这类项目只能整体交给构建系统处理再由 Compiler Explorer 定位产物、反汇编、执行与后处理。实现上每种构建系统对应一个驱动driver构建系统驱动类清单文件适用语言CMakeCMakeBuildSystemlib/build-systems/cmake.tsCMakeLists.txtC、C、Fortran、CUDA、汇编、RustCargoCargoBuildSystemlib/build-systems/cargo.tsCargo.tomlRustMavenMavenBuildSystemlib/build-systems/maven.tspom.xmlJava、KotlinMakeMakeBuildSystemlib/build-systems/make.tsMakefile所有语言compatibleLanguageIds: all每个驱动在 lib/build-systems/index.ts 中注册为单例并可通过getBuildSystem(id)按 id 查找。驱动所依赖的前端与后端共知信息显示名、清单文件名、清单语言、兼容语言、默认参数、输入框占位符、默认产物名集中定义在 shared/build-systems.ts 的BuildSystems描述符表中前端和后端共用同一份。后端buildProject()的编排流程入口与委托关系整条流程由BaseCompiler.buildProject()lib/base-compiler.ts编排CMake 专属部分由CMakeBuildSystem提供而旧有的BaseCompiler.cmake()如今只是对它的一行薄委托lib/base-compiler.tsreturn await this.buildProject(cmakeBuildSystem, files, parsedRequest, bypassCache);buildProject()的完整执行序列如下可行性检查与请求默认值调用buildSystem.getUnsupportedReason(compiler)拒绝不支持的编译器例如不支持产二进制的编译器再通过applyRequestDefaults()强制置位filters.binary与filters.dontMaskFilenames——项目构建要产出可反汇编的产物而诊断信息中的文件名是用户自己的不应被打码。准备目录与写入文件创建临时目录将主源码写成清单文件如CMakeLists.txt并写入全部附加文件writeProjectFiles()随后mkdir build/prepareBuildDirectory()。构造缓存键并尝试缓存命中getBuildProjectCacheKey()生成以api: cmake取自驱动 id区分的缓存键在占用编译队列槽位之前先尝试从缓存加载预构建的可执行包fetchExecutablePackage。注意源码中特意注释这个顺序保证了缓存命中的请求不会白白排队。只有真正需要跑编译器的构建才会计入等待超时——env.enqueue(..., {abandonIfStale: !packaged})。按计划执行构建步骤通过doBuildstepAndAddToResult()依次执行BuildPlan.steps。CMake 的计划是两步configure 步骤执行ceProps(cmake)指定的 cmake可选-GNinja——由ceProps(useninja)决定外加工具链参数和用户的自定义构建系统参数然后cmake --build .。按约定定位产物getExecutableFilename(dirPath/build, outputFilebase, key)即默认寻找build/output.s除非backendOptions.customOutputFilename覆盖CMake 驱动通过编译器派生该名字Windows 编译器会要.exe而不是直接读取描述符的defaultArtifactName。反汇编与后处理checkOutputFileAndDoPostProcess()反汇编产物可选地在本地或远程执行RemoteExecutionQuery随后运行afterCmakeCompilation()工具、opt/stack-usage 输出、缓存写入并清理临时目录。编译器通过环境变量注入而非命令行参数这是整套机制的关键设计构建系统内部调用的是$(CXX)、$(CC)之类的宏所以选中哪个编译器必须通过环境变量传达getCmakeBaseEnv()设置CXX/CCC、FCFortran、CUDACXXCUDA、AS汇编、RUSTCRust其余情况用CC。getCompilerEnvironmentVariables()设置CXXFLAGS/FFLAGS/CUDAFLAGS/CFLAGS/RUSTFLAGS。createCmakeExecParams()设置LDFLAGS与ldPath。一个容易踩坑的细节是configure 与 build 两步刻意共享同一个env对象——createCmakeExecParams()只做浅拷贝因此 build 步骤才能看到CXXFLAGS等标志如果各自深拷贝第二步就拿不到用户通过CXXFLAGS传的编译选项了。需要偏离默认行为的编译器通过钩子覆盖实现这些钩子定义在BaseCompiler上由CMakeBuildSystem回调getExtraCMakeArgs()win32-vc、llvm-mos使用、getCMakeExtToolchainParam()llvm-mos使用、环境变量注入cc65使用。周边管道一览原文档给出了围绕核心编排的完整设施清单整理如下关注点位置HTTP 路由POST /api/compiler/:id/build/:buildSystem以及保留的原始/cmake路由lib/handlers/compile.ts子服务器代理compilerInfo.cmakePath与buildPathtypes/compiler.interfaces.ts在 lib/compiler-finder.ts 中设置队列 workerRemoteCompilationRequest.buildSystem或旧布尔字段isCMakelib/compilation/sqs-compilation-queue.ts消息生产方在本仓库之外缓存键CmakeCacheKey的api: cmake判别字段types/compilation/compilation.interfaces.ts统计以构建系统 id 作为 build methodlib/stats.ts指标ce_project_build_*按构建系统打标签以及旧的ce_cmake_*与两者的 SQS 版本配置cmake与useninja见 etc/config/compiler-explorer.defaults.properties默认值分别为cmake与false后续还有makemake与maven前端tree pane、多文件服务与请求路径前端侧同样围绕构建系统这一概念重构MultifileService持有buildSystem: BuildSystemId | none由旧的isCMakeProject布尔迁移而来主源文件即所选构建系统声明的清单文件。tree 面板static/panes/tree.ts提供构建系统下拉框选项来自getBuildSystemsForLanguage()另有cmakeArgs与customOutputFilename输入框——它们都属于TreeState。compiler 与 executor 两个面板各自维护一套并行的请求路径sendBuildCompile()、pendingBuildRequestSentAt、nextBuildRequest、onBuildResponse()在 tree 面板选择了构建系统时启用。CompilerService.submitBuild()向/build/buildSystem发起 POST。每个清单语言cmake、cargo、maven、makefile在 lib/languages.ts 中是伪语言并在 lib/handlers/api.ts 与 lib/app/config.ts 中被强制暴露给前端——即使没有任何编译器声明该语言tree 模式也总能解析到清单文件的语言以提供语法高亮。短链接通过ClientStateTreelib/clientstate.ts持久化选择过渡期仍同时写isCMakeProjecttest/state/*.json 是这一格式的金样golden fixture。为什么 CMake 没有直接泛化四个固化假设外围管道路由、代理、队列、缓存、统计是容易的部分。真正的难点在于旧的cmake()实现中固化的四个假设也正是BuildSystemDriver接口要隔离的四个维度编译器通过 C 风格环境变量选择。Cargo 要的是RUSTC/CARGOMaven 要的是JAVA_HOME指向的 JDK——设CC就行并不通用。产物位于约定路径。Cargo 需要--message-formatjson或cargo metadata才能发现target/debug/nameMaven 产生以 POM 命名的target/*.jar。注意发现机制提供的只是默认值backendOptions.customOutputFilename是用户亲自挑选要检视哪个产物一个构建出多个 target额外的 bin、库、示例时其余产物同样值得检视——即使驱动能发现自己的产物也必须让该覆盖优先。这个默认值叫defaultArtifactName记录在描述符上前端用它在输出文件输入框做占位符驱动不得偏离。凡是构建产出二进制的构建系统它都是output.s——与普通编译产出的名字一致项目只需记住一个名字Maven 例外产出的是 jar叫output.jar。输出意味着反汇编原生二进制。Cargo 有用的输出是cargo rustc -- --emit asmMaven 则是 javap 风格的字节码根本没有原生二进制。执行意味着直接运行产物。Maven 产物需要java -jar式的启动。因此抽象必须覆盖清单处理、构建步骤、环境、产物发现、后处理与执行形态而不只是运行哪个二进制。分阶段演进计划Phases 0–2 是对用户不可见的纯重构每一阶段都可独立发布。所有阶段目前均已落地原文明确 All the phases below have landed。Phase 0 —— 协议地基shared/build-systems.ts 承载BuildSystemId类型与前后端共享的描述符显示名、清单文件名、清单语言 id、兼容语言 id、默认参数、参数输入框占位符。后端驱动各自携带自己的描述符。线上协议以向后兼容的方式拓宽新增路由POST /api/compiler/:id/build/:buildSystem/cmake永久保留为别名它是 docs/API.md 中记录的公开 API两者都走CompileHandler.handleProjectBuildWith()。backendOptions.buildSystemArgs新增回退到cmakeArgs见getBuildSystemArgs()lib/build-systems/index.ts。前端仍发送cmakeArgs因此缓存键在 Phase 2 切换前保持不变。CmakeCacheKey.api由驱动 id 设置对 CMake 仍是cmake既有缓存与可执行包哈希全部有效。RemoteCompilationRequest.buildSystem与仍然生效的isCMake并存——消息生产方在另一仓库滚动部署期间两者必须都能工作。compilerInfo.buildPath与cmakePath并存且为可选字段——子服务器若运行旧版本既没有该字段也没有该路由CMake 出于同样原因继续代理到cmakePath。统计以构建系统 id 作为 build method。ce_cmake_*计数器继续统计 CMake 以维持既有仪表盘ce_project_build_*/ce_sqs_project_build_*则以build_system标签统计所有构建系统。基础设施依赖各环境的 ALB 对/api/compiler/*/compile与/api/compiler/*/cmake配置了覆盖路由前端开始使用新路由前必须为/api/compiler/*/build/*加上同样的覆盖对应 infra 仓库的 issue #2269。Phase 1 —— 抽取构建系统驱动BaseCompiler.cmake()变成对通用编排器buildProject(buildSystem, files, parsedRequest, bypassCache)的单行委托所有构建系统特有逻辑藏在BuildSystemDriverlib/build-systems/build-system.interfaces.ts之后interface BuildSystemDriver { readonly id: BuildSystemId; readonly descriptor: BuildSystemDescriptor; getUnsupportedReason(compiler: BaseCompiler): Promisestring | undefined; applyRequestDefaults(compiler: BaseCompiler, parsedRequest: ParsedRequest): void; getBuildPath(dirPath: string): string; writeProjectFiles(ctx: BuildContext): Promise{inputFilename: string}; prepareBuildDirectory(ctx: BuildContext): Promisevoid; getBuildPlan(ctx: BuildContext): PromiseBuildPlan; getArtifactFilename(ctx: BuildContext): string; finaliseArtifact(ctx, result, artifactFilename): Promisestring | undefined; prepareExecution(ctx: BuildContext, artifactFilename: string): PromiseExecutionPlan; postProcessArtifact(ctx, result, artifactFilename): PromiseCompilationResult; }同时配套的类型包括BuildContext项目根目录、构建目录、缓存键、解析后的请求、文件、库与选项、工具链路径、用户构建系统参数、BuildPlan有序步骤列表 惰性求值的有效编译选项、BuildSystemStep名称、可执行文件、参数、执行参数、失败占位符、可选的explainFailure诊断器、是否上报编译选项、ExecutionPlan要执行的内容或无法执行的说明。编排器保留所有与构建系统无关的部分临时目录管理、缓存读写、env.enqueue编译队列、远程执行三元组猜测、afterCompilation、清理。原先硬编码的两个构建步骤变成对BuildPlan.steps的循环每一步自带名称、可执行文件、参数、执行参数与失败占位符。每编译器钩子getExtraCMakeArgs()、getCMakeExtToolchainParam()、getCmakeBaseEnv()、createCmakeExecParams()刻意保留 CMake 专属命名如今由CMakeBuildSystem调用而非编排器直接调用——它们是 CMake 概念Cargo 驱动需要的是另一套钩子而不是一个在不同构建系统下含义各异的泛型钩子。win32-vc、llvm-mos、cc65、beebasm这些编译器不受影响。两处顺手泛化writeAllFilesCMake()变成writeProjectFiles(dirPath, manifestFilename, …)getCmakeCacheKey()变成getBuildProjectCacheKey(buildSystem, …)api由驱动 id 设置——对 CMake 仍是cmake缓存构建得以幸存。执行形态钩子Maven 需要java -jar推迟到 Phase 4 再实现。test/build-systems-tests.ts 锁定发出的构建计划——步骤顺序、参数组合、configure 与 build 步骤共享环境、-GNinja与工具链参数。Phase 2 —— 前端枚举而非布尔MultifileServiceState.isCMakeProject: boolean变为buildSystem: BuildSystemId | noneMultifileService与ClientStateTree在读取时双向迁移。过渡期同时写两个字段让旧短链接与混合部署保持一致然后删除布尔并重新生成 test/state/*.json。tree 面板的开关变为构建系统下拉框按当前语言的兼容集合过滤。参数输入框的标签、占位符、默认值都来自描述符-DCMAKE_BUILD_TYPEDebug不再是 static/components.ts 中的全局默认值它现在是 CMake 描述符的defaultArgs。去重 compiler 与 executor 面板共享的 pending/next 请求机制。用编译结果中回显的buildSystem字段替换wasCmake启发式嗅探名为cmake的 buildstep。Phase 3 —— CargoRustCargoBuildSystemlib/build-systems/cargo.ts为rust语言构建Cargo.toml项目同时提供cargo伪语言用于清单高亮以及 TOML 的 Monaco 模式static/modes/toml-mode.ts——Monaco 自带ini但 TOML 的数组表头、数组与内联表在它之下全都渲染错误而Cargo.toml里全是这些结构。几个关键实现点cargo 来自所选编译器自己的工具链而非cargo属性它就是该编译器rustc的同级兄弟。用 1.80 的 cargo 驱动 1.91 的 rustc 会在 lockfile 与 edition 特性上打架源码注释原话。不带 cargo 的 Rust 编译器——gccrs、mrustc、BPF gcc——由getUnsupportedReason拒绝这也是该钩子必须为 async 的原因它需要 stat 检查二进制是否存在。编译器以RUSTC环境变量注入用户选项走RUSTFLAGSCARGO_HOME指向沙箱内部保证 cargo 写出的任何东西都不超出本次编译的生命周期。输出走--message-formatjson-render-diagnosticscargo 把诊断渲染到 stderr用户阅读stdout 留给机器可读的 artifact 记录。驱动解析这些记录然后清空该 stdout让 JSON 永不进入输出面板partitionStdout会保留--help之类的真实输出。cargo 以清单命名其产物所以finaliseArtifact把它构建的东西复制到整个编译流程被告知要等待的路径项目构建多个产物时由customOutputFilename挑选。匹配逻辑还会比较stem无扩展名因此名为output的 bin 也能正确落到约定的output.s。cargo 报告的是它看到的路径沙箱把项目 bind-mount 为/app无沙箱时则是真实临时目录。utils.maskRootdir把两者都归约成相对项目根的路径再重映射到真实根目录toHostPath。库来自 Libraries 面板而不是[dependencies]。Compiler Explorer 的 Rust crates 是从 Conan 拉取的预构建.rlibsetupBuildEnvironment在沙箱构建前把它们解包进项目。cargo 无法自行解析它们——它需要来自 registry 的源码而构建节点没有网络——所以驱动通过CARGO_ENCODED_RUSTFLAGS以--extern直接传给 rustc。于是use rand::Rng;无需在 Cargo.toml 声明任何东西即可工作。自己包的特性features正常工作在[features]中声明然后在 Cargo 参数框传--features、--all-features或--no-default-features。库的特性无法选择因为它的 rlib 以固定的特性集预构建对应 issue #5534。[dependencies]条目因此必然失败而 cargo 自己的报错no matching package named ..., location searched: crates.io index没有提示替代做法所以构建步骤上的explainFailure补充了说明。真正让[dependencies]工作需要在构建节点上提供 vendored crate源码属于基础设施工作需要单独的阶段对应 issue #3763。Phase 4 —— MavenJavaMavenBuildSystemlib/build-systems/maven.ts为 Java 与 Kotlin 构建pom.xml项目清单以maven伪语言按 XML 高亮。关键点JAVA_HOME 来自所选编译器它声明了自己属于哪个 JDKKotlin 用java_homeJava 用runtime两者都没有则回退到编译器可执行文件所在 JDK。找不到 JDK 的编译器被拒绝getUnsupportedReason。源码中getJavaHome()的优先级链是javaHome→javaRuntime的上级目录 →getCompilerRoot()。maven命名要运行的 mvn——与 cargo 不同maven 不属于工具链所以它和cmake一样是属性配置默认值在 etc/config/compiler-explorer.defaults.properties 中为空需部署方显式配置。没有插件 Maven 什么也构建不了而构建节点没有网络所以 infra 的tools.yaml通过在一次性项目中运行每个插件来预热 maven 安装内的仓库。构建把maven.repo.local指向它它只读因此每次编译共享同一份副本。默认 goal 是package用户自命名 goal 时保持不变。namesAGoal()通过形似 goalplugin:goal或三大生命周期之一见源码中MAVEN_PHASES集合来识别用户是否指定了 goal避免逐版维护哪些 flag 取值的可腐烂清单。字节码输出是对target/classes跑 javap复用 Java 编译器自己的处理——这也是驱动设置filters.binary的原因那是 Java 语义中运行 javap的信号与原生二进制无关。执行需要 JVM 启动类这就是prepareExecution钩子的用途它给出与 Java 编译器相同的 JVM 标志以适配沙箱的线程与内存限制。主类通过在每个类文件的常量池中寻找main描述符([Ljava/lang/String;)V来确定。执行参数包括-XX:MaxRAM192m、-Xss512K、-XX:CICompilerCount2、-XX:UseSerialGC等。JavaCompiler.readdir现在是递归的因为带包名的任何东西都会把类放进对应的目录树——构建系统默认就这样做。Clojure 早已为同样原因覆盖过它。dependencies条目无法解析install会因向共享只读仓库写入而失败两者都得到说明而非裸 Maven 报错explainFailure。说明使用步骤的全部输出而非仅 stderr因为 maven 把所有信息都打在 stdout 上。Phase 5 —— Kotlin同样跑在 Maven 上同一个驱动把kotlin加入描述符的语言列表。三处必须让步Kotlin 编译器不属于 JDK它住在自己的kotlin-jvm-x.y.z中所以JAVA_HOME必须来自编译器声明的 JDK而不是从其可执行文件位置推断。kotlin-maven-plugin用自己从仓库解析的编译器编译而不是机器上安装的任何东西任其自然会让编译器选择器沦为装饰——由 pom 决定一切。两个措施合起来保证你选的编译器就是实际运行的编译器版本告诉插件-Dkotlin.version所选 semver遵循只命名一次 Kotlin 版本惯例的 pom 会同时把它用于插件与标准库。它放在用户参数之前坚持要自己版本的 pom 仍然有效。jar 来自所选安装Kotlin 安装的lib/*.jar与 Maven 构件逐字节相同——kotlin-compiler.jar就是org.jetbrains.kotlin:kotlin-compiler连 sha1 都一致。因此驱动把它们符号链接进本次构建私有的仓库并置于最前使用maven.repo.local.tailMaven Resolver 1.9 / Maven 3.9 提供的链式本地仓库。三级链接最近优先本次构建私有 → 编译器旁安装的kotlin-jvm-version-maven由 infra 的tools/kotlin-maven安装只放安装本身没有的构件→ maven 安装自带的共享仓库持有 Java 插件新增 Kotlin 时永不改变。仓库未安装的 Kotlin 会预先被拒并点名有哪些可用版本。从Kotlin 2.2起插件通过 build tools API 驱动编译器索要kotlin-compiler-embeddable——一种把所有东西重新定位打包、任何发行版都不自带的再打包件。它被打包进仓库每版本 54 MiB。它仍是所选的那个 Kotlin 发行版只是 JetBrains 的嵌入版而非命令行版。运行 Kotlin 程序需要标准库与类同侧。Maven 把依赖留在共享仓库执行沙箱看不到所以prepareExecution让 maven 先把它们复制进项目gatherRuntimeDependencies使用maven-dependency-plugin:copy-dependencies构件全名指定因为解析dependency:前缀需要 Maven Central 的元数据离线 maven 读不到。另外它需要比 Java 编译器-Xss136K更大的栈Kotlin 在进入main前嵌套类加载时就会撑爆栈顶达到其标准库的集合——所以用-Xss512K。Phase 6 —— Make全语言MakeBuildSystemlib/build-systems/make.ts针对Makefile跑一次make也是第一个面向每种语言开放的构建系统compatibleLanguageIds: all——Makefile 自己说了要跑什么并不绑定工具链。配置文件中一个make命名二进制用法同cmakeetc/config/compiler-explorer.defaults.properties 中默认为make。它拿到的是交给 CMake 的那套环境经由同一个createCmakeExecParams所选编译器的CXX/CC/FC/CUDACXX/AS/RUSTC携带用户选项与库的对应CXXFLAGS/CFLAGS/FFLAGS/CUDAFLAGS/RUSTFLAGS以及LDFLAGS。于是$(CXX) $(CXXFLAGS) -o output main.cpp这样的 recipe 会用 UI 中选定的编译器编译——这正是提供它的全部意义。当编译器真是 nvcc 时还会设置NVCC。CMake 不需要它但 CUDA Makefile 惯例是$(NVCC)而 make 对此没有内建变量否则会展开为空、recipe 在无编译器状态下运行。以compilerType nvcc为条件守卫clang 也能编译 CUDA把它命名为 NVCC 是 Makefile 无法看穿的谎言。反向陷阱CMake 同样存在对 CUDA 而言$(CXX)是 make 自己内建的g而非此处所选——CE 为 CUDA 语言设置的是CUDACXX。命令行不加任何东西。裸make是默认目标目标、-j、变量覆盖都由用户传。产物是output除非项目自己另有说法因为只有 Makefile 知道自己构建了什么。产物不在时finaliseArtifact给出明确说明并指向输出文件输入框而不是让反汇编器对着缺失文件报错。Makefile 用 Tab 编辑无论缩进设置是什么——以空格开头的 recipe 行只会从 make 得到 missing separator见 static/panes/editor.ts。不可破坏的兼容性契约演进过程中的护栏全部来自原文档任何后续改动都必须遵守/api/compiler/:id/cmake与compilerOptions.cmakeArgs是已文档化的公开 APIdocs/API.md 中/cmake与/build/:buildSystem均有记载。别名它们绝不删除。保留cmake缓存键字面量否则所有已缓存的 CMake 构建全部失效。SQS 消息生产方位于另一仓库跨部署期间必须同时接受新旧两种标志。旧短链接必须永远正常渲染。刻意地更新 test/state/*.json而非顺带为之。如何验证与进一步阅读后端驱动行为由 test/build-systems-tests.ts 锁定它覆盖构建计划的结构步骤顺序、参数组合、configure/build 共享环境、-GNinja、工具链参数、getBuildSystemArgs()的buildSystemArgs/cmakeArgs回退、各驱动的getUnsupportedReason与 artifact 协调逻辑Cargo 的finaliseArtifact、Maven 的namesAGoal/explainFailure、Make 的产物缺失说明以及 shared/build-systems.ts 的描述符查找与语言兼容过滤。协议层面docs/API.md 记录了/cmake与/build/buildSystem两个端点的请求/响应体compilerOptions.buildSystemArgs与cmakeArgs的取舍亦在其中。想新增一种构建系统例如 Gradle从三条线索出发即可在 shared/build-systems.ts 注册描述符id 一旦出现于 API 路由、缓存键api与统计中即不可再改、在 lib/build-systems/ 实现BuildSystemDriver并注册进 lib/build-systems/index.ts 的buildSystems表、再补齐清单语言的伪语言注册与前端 tree 面板兼容集合即可。【免费下载链接】compiler-explorerRun compilers interactively from your web browser and interact with the assembly项目地址: https://gitcode.com/gh_mirrors/co/compiler-explorer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考