Chapter 41Cross Compilation And Wasm

跨编译与WASM

概述

在通过性能分析和保护措施收紧我们的反馈循环之后,40我们准备将这些二进制文件发布到其他平台。本章将介绍目标发现、本机交叉编译和发出WASI模块的基本要素,使用我们之前依赖的相同CLI仪表。#入口点和命令结构

下一章将这些机制转换为完整的WASI项目,因此将其视为您的实践预飞行检查。42

学习目标

  • 解释目标三元组并查询Zig的内置元数据以获取备用架构。Query.zig
  • 使用zig build-exe交叉编译本机可执行文件,并在不离开Linux的情况下验证工件。
  • 生成与本机代码共享相同源的WASI二进制文件,为项目构建管道做好准备。#Command-line-flags

映射目标三元组

Zig的@import("builtin")公开了编译器当前对世界的看法,而std.Target.Query.parse允许您检查假设目标而无需构建它们。Target.zig

这是在您操作zig build之前定制构建图或ENT文件的基础。

理解目标结构

在解析目标三元组之前,理解Zig如何在内部表示编译目标很有价值。下图显示了完整的std.Target结构:

graph TB subgraph "std.Target 结构" TARGET["std.Target"] CPU["cpu: Cpu"] OS["os: Os"] ABI["abi: Abi"] OFMT["ofmt: ObjectFormat"] DYNLINKER["dynamic_linker: DynamicLinker"] TARGET --> CPU TARGET --> OS TARGET --> ABI TARGET --> OFMT TARGET --> DYNLINKER end subgraph "CPU 组件" CPU --> ARCH["arch: Cpu.Arch"] CPU --> MODEL["model: *const Cpu.Model"] CPU --> FEATURES["features: Feature.Set"] ARCH --> ARCHEX["x86_64, aarch64, wasm32, 等"] MODEL --> MODELEX["通用, 本机, 特定变体"] FEATURES --> FEATEX["CPU 特性标志"] end subgraph "OS 组件" OS --> OSTAG["tag: Os.Tag"] OS --> VERSION["version_range: VersionRange"] OSTAG --> OSEX["linux, windows, macos, wasi, 等"] VERSION --> VERUNION["linux: LinuxVersionRange<br/>windows: WindowsVersion.Range<br/>semver: SemanticVersion.Range<br/>none: void"] end subgraph "ABI 和格式" ABI --> ABIEX["gnu, musl, msvc, none, 等"] OFMT --> OFMTEX["elf, macho, coff, wasm, c, spirv"] end

此结构揭示了目标三元组如何映射到具体配置。当您指定-target wasm32-wasi时,您将CPU架构设置为wasm32,OS标签设置为wasi,隐式将ObjectFormat设置为wasm。三元组x86_64-windows-gnu映射到架构x86_64,OSwindows,ABIgnu和格式coff(Windows PE)。

每个组件影响代码生成:CPU架构确定指令集和调用约定,OS标签选择系统调用接口和运行时期望,ABI指定调用约定和名称修饰,ObjectFormat选择链接器(Linux为ELF,Darwin为Mach-O,Windows为COFF,Web/WASI为WASM)。理解这种映射有助于您解码std.Target.Query.parse结果,预测交叉编译行为,并排除目标特定问题。CPU特性字段捕获优化器用于代码生成的架构特定功能(x86_64上的AVX,ARM上的SIMD)。

目标解析流程

目标查询(用户输入)通过系统过程解析为具体目标:

graph TB subgraph "解析流程" QUERY["std.Target.Query<br/>带默认值的用户输入"] RESOLVE["resolveTargetQuery()"] TARGET["std.Target<br/>完全解析"] QUERY --> RESOLVE RESOLVE --> TARGET end subgraph "查询来源" CMDLINE["-target 标志<br/>命令行"] DEFAULT["本机检测<br/>std.zig.system"] MODULE["Module.resolved_target"] CMDLINE --> QUERY DEFAULT --> QUERY end subgraph "本机检测" DETECT["std.zig.system 检测"] CPUDETECT["CPU: cpuid, /proc/cpuinfo"] OSDETECT["OS: uname, NT 版本"] ABIDETECT["ABI: ldd, 平台默认值"] DETECT --> CPUDETECT DETECT --> OSDETECT DETECT --> ABIDETECT end TARGET --> COMP["Compilation.root_mod<br/>.resolved_target.result"]

目标查询来自三个来源:命令行-target标志(显式用户选择)、未指定目标时的本机检测(通过cpuid或/proc/cpuinfo读取主机CPU,通过uname或NT API读取OS,通过ldd或平台默认值读取ABI)或构建脚本中的模块配置。

resolveTargetQuery()函数通过填充所有缺失的细节将查询(可能包含"native"或"default"占位符)转换为完全具体的std.Target实例。此解析在编译初始化期间进行,在任何代码生成发生之前。

当您省略-target时,Zig会自动检测您的主机系统并构建本机目标。当您指定像wasm32-wasi这样的部分三元组时,解析会填充ABI(通常WASI为musl)和对象格式(wasm)。然后解析的目标流向编译模块,在那里控制代码生成的各个方面,从指令选择到运行时库选择。

示例:从代码比较主机和交叉目标

该示例内省主机三元组,然后解析两个交叉目标,打印解析的架构、OS和ABI。

Zig
// Import standard library for target querying and printing
const std = @import("std");
// Import builtin module to access compile-time host target information
const builtin = @import("builtin");

/// Entry point that demonstrates target discovery and cross-platform metadata inspection.
/// This example shows how to introspect both the host compilation target and parse
/// hypothetical cross-compilation targets without actually building for them.
pub fn main() void {
    // Print the host target triple (architecture-OS-ABI) by accessing builtin.target
    // This shows the platform Zig is currently compiling for
    std.debug.print(
        "host triple: {s}-{s}-{s}\n",
        .{
            @tagName(builtin.target.cpu.arch),
            @tagName(builtin.target.os.tag),
            @tagName(builtin.target.abi),
        },
    );

    // Display the pointer width for the host target
    // @bitSizeOf(usize) returns the size in bits of a pointer on the current platform
    std.debug.print("pointer width: {d} bits\n", .{@bitSizeOf(usize)});

    // Parse a WASI target query from a target triple string
    // This demonstrates how to inspect cross-compilation targets programmatically
    const wasm_query = std.Target.Query.parse(.{ .arch_os_abi = "wasm32-wasi" }) catch unreachable;
    describeQuery("wasm32-wasi", wasm_query);

    // Parse a Windows target query to show another cross-compilation scenario
    // The triple format follows: architecture-OS-ABI
    const windows_query = std.Target.Query.parse(.{ .arch_os_abi = "x86_64-windows-gnu" }) catch unreachable;
    describeQuery("x86_64-windows-gnu", windows_query);

    // Print whether the host target is configured for single-threaded execution
    // This compile-time constant affects runtime library behavior
    std.debug.print("single-threaded: {}\n", .{builtin.single_threaded});
}

/// Prints the resolved architecture, OS, and ABI for a given target query.
/// This helper demonstrates how to extract and display target metadata, using
/// the host target as a fallback when the query doesn't specify certain fields.
fn describeQuery(label: []const u8, query: std.Target.Query) void {
    std.debug.print(
        "query {s}: arch={s} os={s} abi={s}\n",
        .{
            label,
            // Fall back to host architecture if query doesn't specify one
            @tagName((query.cpu_arch orelse builtin.target.cpu.arch)),
            // Fall back to host OS if query doesn't specify one
            @tagName((query.os_tag orelse builtin.target.os.tag)),
            // Fall back to host ABI if query doesn't specify one
            @tagName((query.abi orelse builtin.target.abi)),
        },
    );
}
运行
Shell
$ zig run 01_target_matrix.zig
输出
Shell
host triple: x86_64-linux-gnu
pointer width: 64 bits
query wasm32-wasi: arch=wasm32 os=wasi abi=gnu
query x86_64-windows-gnu: arch=x86_64 os=windows abi=gnu
single-threaded: false

解析器遵循与-Dtargetzig build-exe -target相同的语法;在调用编译器之前循环输出以构建构建配置。

跨编译本机可执行文件

有了三元组,交叉编译就是交换目标标志的问题。Zig 0.15.2附带自包含的libc集成,因此在Linux上生成Windows或macOS二进制文件不再需要额外的SDK。v0.15.2

使用file或类似工具来确认产物,而无需启动另一个操作系统。

示例:从Linux到Windows的

我们保持源代码相同,在本机运行以进行健全性检查,然后发出Windows PE二进制并就地检查它。

Zig

// Import the standard library for printing and platform utilities
const std = @import("std");
// Import builtin to access compile-time target information
const builtin = @import("builtin");

// Entry point that demonstrates cross-compilation by displaying target platform information
pub fn main() void {
    // Print the target platform's CPU architecture, OS, and ABI
    // Uses builtin.target to access compile-time target information
    std.debug.print("hello from {s}-{s}-{s}!\n", .{
        @tagName(builtin.target.cpu.arch),
        @tagName(builtin.target.os.tag),
        @tagName(builtin.target.abi),
    });

    // Retrieve the platform-specific executable file extension (e.g., ".exe" on Windows, "" on Linux)
    const suffix = std.Target.Os.Tag.exeFileExt(builtin.target.os.tag, builtin.target.cpu.arch);
    std.debug.print("default executable suffix: {s}\n", .{suffix});
}
运行
Shell
$ zig run 02_cross_greeter.zig
输出
Shell
hello from x86_64-linux-gnu!
default executable suffix:
交叉编译
Shell
$ zig build-exe 02_cross_greeter.zig -target x86_64-windows-gnu -OReleaseFast -femit-bin=greeter-windows.exe
$ file greeter-windows.exe
输出
Shell
greeter-windows.exe: PE32+ executable (console) x86-64, for MS Windows, 7 sections

当您需要用于较旧硬件的可移植二进制文件时,将-target-mcpu=baseline配对;上面的std.Target.Query显示Zig将

发出WASI模块

WebAssembly系统接口(WASI)构建与本机管道共享大部分内容,但对象格式不同。相同的Zig源代码可以在Linux上打印诊断,并在交叉编译时发出.wasm有效负载,这要归功于本版本中引入的共享libc片段。

对象格式和链接器选择

在生成WASI二进制文件之前,理解对象格式如何确定编译输出很重要。下图显示了ABI和对象格式之间的关系:

graph TB subgraph "Common ABIs" ABI["Abi enum"] ABI --> GNU["gnu<br/>GNU toolchain"] ABI --> MUSL["musl<br/>musl libc"] ABI --> MSVC["msvc<br/>Microsoft Visual C++"] ABI --> NONE["none<br/>freestanding"] ABI --> ANDROID["android, gnueabi, etc<br/>platform variants"] end subgraph "Object Formats" OFMT["ObjectFormat enum"] OFMT --> ELF["elf<br/>Linux, BSD"] OFMT --> MACHO["macho<br/>Darwin systems"] OFMT --> COFF["coff<br/>Windows PE"] OFMT --> WASM["wasm<br/>WebAssembly"] OFMT --> C["c<br/>C source output"] OFMT --> SPIRV["spirv<br/>Shaders"] end

对象格式确定Zig使用哪个链接器实现来生成最终二进制文件。ELF(可执行和可链接格式)用于Linux和BSD系统,生成.so共享库和标准可执行文件。Mach-O针对Darwin系统(macOS、iOS),生成.dylib库和Mach可执行文件。COFF(通用对象文件格式)在针对Windows时生成Windows PE二进制(.exe.dll)。WASM(WebAssembly)是一种独特格式,为Web浏览器和WASI运行时生成.wasm模块。与传统格式不同,WASM模块是设计用于沙盒执行的平台无关字节码。CSPIRV是专门的:C输出源代码以与C构建系统集成,而SPIRV生成GPU着色器字节码。

当您为-target wasm32-wasi构建时,Zig选择WASM对象格式并调用WebAssembly链接器(link/Wasm.zig),该链接器处理WASM特定概念,如函数导入/导出、内存管理和表初始化。这与ELF链接器(符号解析、重定位)或COFF链接器(导入表、资源部分)根本不同。相同的源代码透明地编译为不同的对象格式——无论目标是本机Linux(ELF)、Windows(COFF)还是WASI(WASM),您的Zig代码都保持相同。

示例:单一源、本机运行、WASI产物

我们的管道记录执行阶段并基于builtin.target.os.tag分支,因此WASI构建宣布自己的入口点。

Zig

// Import standard library for debug printing capabilities
const std = @import("std");
// Import builtin module to access compile-time target information
const builtin = @import("builtin");

/// Prints a stage name to stderr for tracking execution flow.
/// This helper function demonstrates debug output in cross-platform contexts.
fn stage(name: []const u8) void {
    std.debug.print("stage: {s}\n", .{name});
}

/// Demonstrates conditional compilation based on target OS.
/// This example shows how Zig code can branch at compile-time depending on
/// whether it's compiled for WASI (WebAssembly System Interface) or native platforms.
/// The execution flow changes based on the target, illustrating cross-compilation capabilities.
pub fn main() void {
    // Simulate initial argument parsing stage
    stage("parse-args");
    // Simulate payload rendering stage
    stage("render-payload");

    // Compile-time branch: different entry points for WASI vs native targets
    // This demonstrates how Zig handles platform-specific code paths
    if (builtin.target.os.tag == .wasi) {
        stage("wasi-entry");
    } else {
        stage("native-entry");
    }

    // Print the actual OS tag name for the compilation target
    // @tagName converts the enum value to its string representation
    stage(@tagName(builtin.target.os.tag));
}
运行
Shell
$ zig run 03_wasi_pipeline.zig
输出
Shell
stage: parse-args
stage: render-payload
stage: native-entry
stage: linux
WASI build
Shell
$ zig build-exe 03_wasi_pipeline.zig -target wasm32-wasi -OReleaseSmall -femit-bin=wasi-pipeline.wasm
$ ls -lh wasi-pipeline.wasm
输出
Shell
-rwxr--r-- 1 zkevm zkevm 4.6K Nov  6 13:40 wasi-pipeline.wasm

使用您喜欢的运行时(Wasmtime、Wasmer、浏览器)运行生成的模块,或将其交给下一章的构建图。无需源代码更改。

注意事项与警告

  • zig targets提供支持的权威三元组矩阵。在分派作业之前编写脚本以验证您的构建矩阵。
  • 某些目标默认为ReleaseSmall风格的安全。当您需要跨架构一致的运行时检查时,显式设置-Doptimize#releasefast
  • 交叉链接到glibc时,填充ZIG_LIBC或使用zig fetch缓存sysroot产物,以便链接器不会意外访问主机头。

练习

  • 使用--cpu--os标志扩展问候程序,然后为x86_64-macos-gnuaarch64-linux-musl发出二进制文件,并使用ls -lh捕获它们的大小。
  • 修改WASI管道以通过std.json.stringify发出JSON,然后在WASI运行时运行它并捕获输出以进行回归测试。json.zig
  • 编写一个build.zig步骤,该步骤循环遍历目标三元组列表并为每个目标调用一次addExecutable,使用std.Target.Query帮助程序打印人类友好的标签。22

替代方案与边缘案例

  • LLVM支持的目标可能仍然与Zig的自托管代码生成不同。当您遇到新兴架构时,回退到-fllvm
  • WASI禁止许多系统调用和动态分配模式。保持日志简洁或门控,以避免超出导入预算。
  • Windows交叉编译默认选择GNU工具链。如果您打算链接MSVC提供的库,请添加-msvc或切换ABI。20

Help make this chapter better.

Found a typo, rough edge, or missing explanation? Open an issue or propose a small improvement on GitHub.